《视频篇》java实现下载hls(m3u8+ts)实时流并进行合并mp4

链接:https://blog.csdn.net/qq_41604890/article/details/130143355

首先需要了解什么是HLS

简单理解就是, m3u8文件存放着可供客户端播放TS 片段

简单一点说m3u8加密技术就是将原视频分割成n个.ts文件,并用一个key文件对每一个.ts文件加密,其中m3u8文件里面存储了key文件和所有的.ts文件的地址,所以我们要解密这个视频就需要以上三种文件,最后可以利用ffmpeg来合并并解压;

M3U8编码格式

m3u8基本上可以认为就是.m3u格式文件,区别在于,m3u8文件使用UTF-8字符编码。

// m3u文件头,必须放在第一行
#EXTM3U  
// 定义当前m3u8文件中第一个文件的序列号,每个ts文件在m3u8文件中都有固定唯一的序列号
// 该序列号用于在MBR时切换码率进行对齐
#EXT-X-MEDIA-SEQUENCE 
// 每个分片TS的最大的时长
#EXT-X-TARGETDURATION 
// 是否允许cache
#EXT-X-ALLOW-CACHE
// m3u8文件结束符
#EXT-X-ENDLIST 
// 分片TS的信息,如时长,带宽等
#EXTINF 
// 定义加密方式和key文件的url,用于取得16bytes的key文件解码ts文件
#EXT-X-KEY
// 提供关于PlayList的可变性的信息,对整个PlayList文件有效,是可选项。
// 格式如下:#EXT-X-PLAYLIST-TYPE:VOD(或者EVENT)。VOD表示服务器不能改变PlayList 文件;
// EVENT则表示服务器不能改变或是删除PlayList文件中的任何部分,但是可以向该文件中增加新的一行内容。
#EXT-X-PLAYLIST-TYPE 

下载hls(m3u8+ts)流程

判断是否需要解密

如果内容含有#EXT-X-KEY标签,则说明这个链接是需要进行ts文件解密的,然后通过下面的.m3u8的if语句获取含有密钥以及ts片段的链接。

依次下载ts文件

注意:
如果是下载已存在的视频可以使用多线程的方式下载ts文件,但是下载的是当前正在直播实时流的时候,单线程即可

将下载的ts文件合并为mp4格式

注意:合并时保证ts文件为连续的
通过 #EXT-X-MEDIA-SEQUENCE 当前第一个TS分片的序列号 来判断下载的ts文件的连续性

代码

import java.io.*;
import java.net.URL;
import java.net.URLConnection;
import java.nio.charset.StandardCharsets;
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;

public class Test {


	/**
	 * 下载索引文件信息
	 *
	 * @param m3u8UrlPath
	 * @return 索引文件信息
	 */
	public static String getM3u8FileIndexInfo(String m3u8UrlPath) {
		try (BufferedReader in = new BufferedReader(new InputStreamReader(new URL(m3u8UrlPath).openStream(), StandardCharsets.UTF_8))) {
			StringBuilder content = new StringBuilder();
			String line;
			while ((line = in.readLine()) != null) {
				content.append(line).append("\n");
			}
			return content.toString();
		} catch (IOException e) {
			e.printStackTrace();
		}
		return null;
	}


	/**
	 * 解析索引文件中的ts列表信息
	 */
	public static List<String> analysisTsList(String m3u8FileIndexInfo) {
		Pattern pattern = Pattern.compile(".*ts");
		Matcher ma = pattern.matcher(m3u8FileIndexInfo);
		List<String> list = new ArrayList<>();
		while (ma.find()) {
			list.add(ma.group());
		}
		return list;
	}


	public static void downLoadIndexFile(List<String> tsList, String folderPath, String preUrlPath) {
		for (int i = 0; i < tsList.size(); i++) {
			String ts = tsList.get(i);
			String fileOutPath = folderPath + File.separator + ts;
			try {
				downloadTs(preUrlPath + "/" + ts, fileOutPath);
				System.out.println("下载成功:" + (i + 1) + "/" + tsList.size());
			} catch (Exception e) {
				System.err.println("下载失败:" + (i + 1) + "/" + tsList.size());
			}
		}
	}

	/**
	 * 下载ts文件
	 *
	 * @param fullUrlPath
	 * @param fileOutPath
	 */
	public static void downloadTs(String fullUrlPath, String fileOutPath) {
		try (InputStream inStream = new URL(fullUrlPath).openConnection().getInputStream();
			 FileOutputStream fs = new FileOutputStream(fileOutPath)) {
			int byteread;
			byte[] buffer = new byte[1204];
			while ((byteread = inStream.read(buffer)) != -1) {
				fs.write(buffer, 0, byteread);
			}
		} catch (IOException e) {
			e.printStackTrace();
		}
	}


	public static String composeFile(List<String> tsList, String folderPath){
		String fileOutPath = folderPath + File.separator + UUID.randomUUID() + ".mp4";
		try (FileOutputStream fileOutputStream = new FileOutputStream(new File(fileOutPath))){
			byte[] bytes = new byte[1024];
			int length;
			for (String nodePath : tsList) {
				File file = new File(nodePath);
				if (!file.exists()) {
					continue;
				}
				try (FileInputStream fis = new FileInputStream(file);) {
					while ((length = fis.read(bytes)) != -1) {
						fileOutputStream.write(bytes, 0, length);
					}
					// 删除该临时文件

				} catch (IOException e) {
					e.printStackTrace();
				}
			}
		} catch (Exception e) {
			e.printStackTrace();
		}
		return fileOutPath;
	}

	public static void main(String[] args) {
		// m3u8下載地址
		String m3u8UrlPath = "https://dh5.cntv.myalicdn.com/asp/h5e/hls/1200/0303000a/3/default/cdd3da535c12447a8cdb7c8ca949b2f6/1200.m3u8";

		// 下载索引文件信息
		String m3u8FileIndexInfo = getM3u8FileIndexInfo(m3u8UrlPath);
		System.out.println("========================");
		System.out.println(m3u8FileIndexInfo);
		System.out.println("========================");

		// 解析索引文件中的ts列表信息
		List<String> tsList = analysisTsList(m3u8FileIndexInfo);

		// 这里为了测试就先下载10个吧
		tsList = tsList.subList(0, 3);

		System.out.println(tsList);

		// 依次下载ts文件

		// 下载到本地的磁盘位置
		String folderPath = "D:/file";
		// 请求ts文件的下载地址
		String preUrlPath = "https://dh5.cntv.myalicdn.com/asp/h5e/hls/1200/0303000a/3/default/cdd3da535c12447a8cdb7c8ca949b2f6";
		downLoadIndexFile(tsList, folderPath, preUrlPath);
		String mp4Path = composeFile(tsList, folderPath);
		System.out.println(mp4Path);
	}

}

测试

可以去央视网 http://tv.cctv.com/ ,找一个视频播放,打开F12查看请求的接口,找到m3u8请求地址

image

点击查看代码
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-TARGETDURATION:15
#EXT-X-MEDIA-SEQUENCE:0
#EXT-X-PLAYLIST-TYPE:VOD
#EXTINF:11.120000,
0.ts
#EXTINF:9.680000,
1.ts
#EXTINF:9.360000,
2.ts
#EXTINF:11.640000,
3.ts
#EXTINF:10.120000,
4.ts
#EXTINF:8.520000,
5.ts
#EXTINF:10.000000,
6.ts
#EXTINF:10.600000,
7.ts
#EXTINF:12.360000,
8.ts
#EXTINF:9.640000,
9.ts
#EXTINF:8.360000,
10.ts
#EXTINF:8.760000,
11.ts
#EXTINF:13.400000,
12.ts
#EXTINF:9.200000,
13.ts
#EXTINF:9.920000,
14.ts
#EXTINF:9.560000,
15.ts
#EXTINF:8.160000,
16.ts
#EXTINF:9.720000,
17.ts
#EXTINF:13.440000,
18.ts
#EXTINF:9.640000,
19.ts
#EXTINF:8.480000,
20.ts
#EXTINF:9.880000,
21.ts
#EXTINF:9.920000,
22.ts
#EXTINF:9.160000,
23.ts
#EXTINF:10.960000,
24.ts
#EXTINF:8.720000,
25.ts
#EXTINF:10.840000,
26.ts
#EXTINF:13.080000,
27.ts
#EXTINF:6.680000,
28.ts
#EXTINF:10.000000,
29.ts
#EXTINF:12.440000,
30.ts
#EXTINF:8.440000,
31.ts
#EXTINF:9.240000,
32.ts
#EXTINF:11.040000,
33.ts
#EXTINF:8.080000,
34.ts
#EXTINF:10.080000,
35.ts
#EXTINF:11.280000,
36.ts
#EXTINF:10.000000,
37.ts
#EXTINF:8.800000,
38.ts
#EXTINF:10.160000,
39.ts
#EXTINF:13.360000,
40.ts
#EXTINF:6.840000,
41.ts
#EXTINF:13.680000,
42.ts
#EXTINF:5.760000,
43.ts
#EXTINF:10.320000,
44.ts
#EXTINF:13.200000,
45.ts
#EXTINF:7.800000,
46.ts
#EXTINF:8.680000,
47.ts
#EXTINF:12.640000,
48.ts
#EXTINF:9.600000,
49.ts
#EXTINF:8.440000,
50.ts
#EXTINF:11.360000,
51.ts
#EXTINF:7.960000,
52.ts
#EXTINF:10.840000,
53.ts
#EXTINF:9.240000,
54.ts
#EXTINF:10.800000,
55.ts
#EXTINF:11.200000,
56.ts
#EXTINF:9.640000,
57.ts
#EXTINF:9.880000,
58.ts
#EXTINF:8.840000,
59.ts
#EXTINF:10.960000,
60.ts
#EXTINF:10.440000,
61.ts
#EXTINF:10.960000,
62.ts
#EXTINF:8.160000,
63.ts
#EXTINF:9.160000,
64.ts
#EXTINF:10.600000,
65.ts
#EXTINF:13.320000,
66.ts
#EXTINF:5.880000,
67.ts
#EXTINF:10.920000,
68.ts
#EXTINF:10.200000,
69.ts
#EXTINF:8.920000,
70.ts
#EXTINF:10.800000,
71.ts
#EXTINF:10.480000,
72.ts
#EXTINF:9.160000,
73.ts
#EXTINF:9.920000,
74.ts
#EXTINF:10.760000,
75.ts
#EXTINF:11.480000,
76.ts
#EXTINF:7.480000,
77.ts
#EXTINF:11.000000,
78.ts
#EXTINF:10.280000,
79.ts
#EXTINF:9.640000,
80.ts
#EXTINF:12.480000,
81.ts
#EXTINF:8.600000,
82.ts
#EXTINF:7.840000,
83.ts
#EXTINF:13.080000,
84.ts
#EXTINF:9.120000,
85.ts
#EXTINF:8.760000,
86.ts
#EXTINF:9.160000,
87.ts
#EXTINF:11.280000,
88.ts
#EXTINF:9.000000,
89.ts
#EXTINF:10.800000,
90.ts
#EXTINF:11.200000,
91.ts
#EXTINF:9.760000,
92.ts
#EXTINF:8.880000,
93.ts
#EXTINF:9.600000,
94.ts
#EXTINF:9.960000,
95.ts
#EXTINF:11.520000,
96.ts
#EXTINF:9.480000,
97.ts
#EXTINF:9.960000,
98.ts
#EXTINF:10.960000,
99.ts
#EXTINF:11.680000,
100.ts
#EXTINF:5.800000,
101.ts
#EXTINF:12.920000,
102.ts
#EXTINF:7.560000,
103.ts
#EXTINF:10.600000,
104.ts
#EXTINF:11.400000,
105.ts
#EXTINF:7.720000,
106.ts
#EXTINF:9.880000,
107.ts
#EXTINF:11.200000,
108.ts
#EXTINF:10.440000,
109.ts
#EXTINF:8.720000,
110.ts
#EXTINF:11.400000,
111.ts
#EXTINF:9.040000,
112.ts
#EXTINF:9.320000,
113.ts
#EXTINF:10.200000,
114.ts
#EXTINF:11.120000,
115.ts
#EXTINF:11.080000,
116.ts
#EXTINF:8.240000,
117.ts
#EXTINF:9.160000,
118.ts
#EXTINF:10.480000,
119.ts
#EXTINF:9.520000,
120.ts
#EXTINF:10.480000,
121.ts
#EXTINF:11.640000,
122.ts
#EXTINF:9.080000,
123.ts
#EXTINF:9.840000,
124.ts
#EXTINF:9.280000,
125.ts
#EXTINF:12.840000,
126.ts
#EXTINF:9.600000,
127.ts
#EXTINF:7.320000,
128.ts
#EXTINF:13.200000,
129.ts
#EXTINF:7.240000,
130.ts
#EXTINF:10.160000,
131.ts
#EXTINF:11.680000,
132.ts
#EXTINF:9.760000,
133.ts
#EXTINF:8.960000,
134.ts
#EXTINF:12.520000,
135.ts
#EXTINF:8.160000,
136.ts
#EXTINF:8.680000,
137.ts
#EXTINF:11.360000,
138.ts
#EXTINF:11.360000,
139.ts
#EXTINF:9.600000,
140.ts
#EXTINF:7.320000,
141.ts
#EXTINF:11.320000,
142.ts
#EXTINF:9.480000,
143.ts
#EXTINF:11.520000,
144.ts
#EXTINF:7.960000,
145.ts
#EXTINF:12.080000,
146.ts
#EXTINF:10.120000,
147.ts
#EXTINF:7.440000,
148.ts
#EXTINF:10.000000,
149.ts
#EXTINF:10.360000,
150.ts
#EXTINF:13.800000,
151.ts
#EXTINF:9.440000,
152.ts
#EXTINF:7.080000,
153.ts
#EXTINF:11.600000,
154.ts
#EXTINF:8.040000,
155.ts
#EXTINF:9.720000,
156.ts
#EXTINF:9.960000,
157.ts
#EXTINF:11.240000,
158.ts
#EXTINF:11.840000,
159.ts
#EXTINF:10.120000,
160.ts
#EXTINF:6.840000,
161.ts
#EXTINF:13.800000,
162.ts
#EXTINF:6.160000,
163.ts
#EXTINF:11.800000,
164.ts
#EXTINF:9.600000,
165.ts
#EXTINF:13.000000,
166.ts
#EXTINF:6.280000,
167.ts
#EXTINF:10.120000,
168.ts
#EXTINF:10.200000,
169.ts
#EXTINF:9.520000,
170.ts
#EXTINF:10.240000,
171.ts
#EXTINF:11.920000,
172.ts
#EXTINF:7.680000,
173.ts
#EXTINF:12.480000,
174.ts
#EXTINF:7.320000,
175.ts
#EXTINF:13.000000,
176.ts
#EXTINF:10.160000,
177.ts
#EXTINF:10.080000,
178.ts
#EXTINF:7.000000,
179.ts
#EXTINF:13.080000,
180.ts
#EXTINF:8.480000,
181.ts
#EXTINF:8.280000,
182.ts
#EXTINF:12.280000,
183.ts
#EXTINF:10.000000,
184.ts
#EXTINF:8.000000,
185.ts
#EXTINF:9.880000,
186.ts
#EXTINF:9.760000,
187.ts
#EXTINF:10.000000,
188.ts
#EXTINF:10.440000,
189.ts
#EXTINF:10.080000,
190.ts
#EXTINF:10.080000,
191.ts
#EXTINF:11.920000,
192.ts
#EXTINF:9.120000,
193.ts
#EXTINF:11.920000,
194.ts
#EXTINF:7.600000,
195.ts
#EXTINF:9.560000,
196.ts
#EXTINF:9.720000,
197.ts
#EXTINF:11.360000,
198.ts
#EXTINF:8.200000,
199.ts
#EXTINF:10.640000,
200.ts
#EXTINF:11.280000,
201.ts
#EXTINF:8.680000,
202.ts
#EXTINF:10.200000,
203.ts
#EXTINF:9.800000,
204.ts
#EXTINF:9.600000,
205.ts
#EXTINF:10.440000,
206.ts
#EXTINF:11.760000,
207.ts
#EXTINF:8.680000,
208.ts
#EXTINF:9.600000,
209.ts
#EXTINF:12.600000,
210.ts
#EXTINF:6.800000,
211.ts
#EXTINF:10.720000,
212.ts
#EXTINF:10.840000,
213.ts
#EXTINF:9.600000,
214.ts
#EXTINF:8.840000,
215.ts
#EXTINF:11.680000,
216.ts
#EXTINF:9.840000,
217.ts
#EXTINF:9.240000,
218.ts
#EXTINF:9.600000,
219.ts
#EXTINF:9.440000,
220.ts
#EXTINF:9.960000,
221.ts
#EXTINF:11.080000,
222.ts
#EXTINF:12.000000,
223.ts
#EXTINF:7.520000,
224.ts
#EXTINF:12.520000,
225.ts
#EXTINF:10.840000,
226.ts
#EXTINF:6.200000,
227.ts
#EXTINF:14.600000,
228.ts
#EXTINF:9.600000,
229.ts
#EXTINF:9.600000,
230.ts
#EXTINF:6.920000,
231.ts
#EXTINF:10.320000,
232.ts
#EXTINF:9.040000,
233.ts
#EXTINF:10.040000,
234.ts
#EXTINF:9.960000,
235.ts
#EXTINF:10.880000,
236.ts
#EXTINF:12.280000,
237.ts
#EXTINF:7.680000,
238.ts
#EXTINF:9.840000,
239.ts
#EXTINF:9.720000,
240.ts
#EXTINF:11.200000,
241.ts
#EXTINF:9.800000,
242.ts
#EXTINF:11.400000,
243.ts
#EXTINF:11.160000,
244.ts
#EXTINF:5.840000,
245.ts
#EXTINF:14.320000,
246.ts
#EXTINF:7.400000,
247.ts
#EXTINF:9.600000,
248.ts
#EXTINF:8.640000,
249.ts
#EXTINF:10.760000,
250.ts
#EXTINF:9.880000,
251.ts
#EXTINF:12.800000,
252.ts
#EXTINF:7.920000,
253.ts
#EXTINF:11.200000,
254.ts
#EXTINF:9.600000,
255.ts
#EXTINF:8.720000,
256.ts
#EXTINF:9.320000,
257.ts
#EXTINF:11.000000,
258.ts
#EXTINF:9.320000,
259.ts
#EXTINF:10.400000,
260.ts
#EXTINF:9.960000,
261.ts
#EXTINF:9.760000,
262.ts
#EXTINF:9.720000,
263.ts
#EXTINF:10.160000,
264.ts
#EXTINF:13.360000,
265.ts
#EXTINF:6.120000,
266.ts
#EXTINF:11.680000,
267.ts
#EXTINF:8.400000,
268.ts
#EXTINF:9.480000,
269.ts
#EXT-X-ENDLIST


ts下载下来后是花屏,应该是音视分离加密了 ,那就这样吧 ~ 既然加密了 咱就不下载了

防盗链说明

IPTV系统中防盗链是很多令人头疼的问题,现在防盗链的方式有很多,比如常见的有动态key、视频地址当盗链处理、p2p私有协议等,这些都各有利弊。其实除了这些还有就是对视频流加密,这种在视频点播中使用的比较多,但在直播中也可实现,即对版权方给的ts或者说m3u8格式的视频进行加密处理,对内容进行帧加密,加密后只能在允许的APP中播放,即使下载到其他APP中也没法播放。

整个使用流程如下:

1、部署点量流媒体系统,在对版权方给的直播视频流进行中转的时候就直接开启加密功能,自动对视频流进行加密处理,加密后直接给出频道地址。

2、将加密后的频道地址放到CMS管理后台,在APP播放器端进行技术对接,使得加密视频只能在特定的APP中使用。而且是对内容帧加密,安全性更高,不留存加密视频播放完即删除。

3、视频一次加密后可在安卓和iOS系统下的APP中使用,方便快捷,配合点量CMS后台管理系统,可实现一个后台对应多平台APP使用。

4、也可以单独对接到现有系统中使用,直接以SDK的形式快速接入,不影响现在用户的使用体验。

posted @ 2024-02-05 13:47  Fusio  阅读(1396)  评论(0编辑  收藏  举报