您现在的位置是:首页 >技术教程 >java spring 实现 下载hls(m3u8+ts)实时流并进行合并mp4和压缩网站首页技术教程

java spring 实现 下载hls(m3u8+ts)实时流并进行合并mp4和压缩

qq_250056868 2023-06-27 04:00:03
简介java spring 实现 下载hls(m3u8+ts)实时流并进行合并mp4和压缩

参考连接

链接: java下载m3u8视频,解密并合并ts(三)

链接: Java 下载 HLS (m3u8) 视频

首先需要了解什么是HLS

链接: HTTP Live Streaming (HLS) - 概念

链接: M3U8是什么

简单理解就是, 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)流程

首先下载m3u8文件

判断是否需要解密

如果内容含有#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("
");
            }
            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请求地址
在这里插入图片描述

#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的形式快速接入,不影响现在用户的使用体验。

风语者!平时喜欢研究各种技术,目前在从事后端开发工作,热爱生活、热爱工作。