HLS(HTTP Live Streaming)是苹果公司主导的基于HTTP的流媒体协议。它将视频切分为数秒一个的TS文件,客户端需要轮询视频文件的索引地址,将一小段一小段的视频组装起来
HLS地址: http://hls.open.ys7.com/openlive/f01018a141094b7fa138b9d0b856507b.hd.m3u8
某一时段的响应:
#EXTM3U
#EXT-X-VERSION:3
#EXT-X-ALLOW-CACHE:NO
#EXT-X-TARGETDURATION:4
#EXT-X-MEDIA-SEQUENCE:15
#EXT-TS-OFFSET-BEGIN:45
#EXTINF:3.997,
http://hzhls01.ys7.com:7888/openlivedata/203751922_1_1/8883db61d09440008b06f7062cdadb31-15.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
#EXT-TS-OFFSET-BEGIN:48
#EXTINF:3.997,
http://hzhls01.ys7.com:7888/openlivedata/203751922_1_1/8883db61d09440008b06f7062cdadb31-16.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
#EXT-TS-OFFSET-BEGIN:51
#EXTINF:3.998,
http://hzhls01.ys7.com:7888/openlivedata/203751922_1_1/8883db61d09440008b06f7062cdadb31-17.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
部分字段解释:
#EXT-X-TARGETDURATION:4
视频片段最长为4秒#EXT-TS-OFFSET-BEGIN:45
当前视频的时间戳是45秒(相对首次播放时间)#EXTINF:3.997
当前视频长度4秒http://...
当前视频片段的下载地址注意,萤石云的HLS协议不够规范,比如视频时长用浮点数,两个视频片段的索引间隔跟单个视频时长不一致,不知是何用意。真实时长以下载到视频文件的时长为准。
Java的HLS解析库主要有以下两个:
然而,这两个库兼容性太差,也可能是因为这个HLS报文不够标准吧,总之两个库没有一个库能解析出来。所以只能手动解析了。
示例代码
package bj;
import ch.qos.logback.classic.Level;
import ch.qos.logback.classic.Logger;
import ch.qos.logback.classic.encoder.PatternLayoutEncoder;
import ch.qos.logback.core.ConsoleAppender;
import ch.qos.logback.core.Context;
import io.vavr.control.Try;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.FileUtils;
import org.apache.commons.lang3.tuple.Triple;
import org.junit.Test;
import org.slf4j.LoggerFactory;
import org.springframework.web.client.RestTemplate;
import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* Created by BaiJiFeiLong@gmail.com at 2018/12/1 上午10:17
*/
@Slf4j
public class HlsTest {
private RestTemplate restTemplate = new RestTemplate();
/**
* 从HLS链接下载一个20秒的视频片段
*
* @throws IOException .
*/
@Test
public void testAlpha() throws IOException {
// Logback配置日志级别为INFO
((Logger) (LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME))).setLevel(Level.INFO);
// 自定义控制台日志格式
// noinspection unchecked
((ConsoleAppender) ((Logger) (LoggerFactory.getLogger(Logger.ROOT_LOGGER_NAME))).getAppender("console")).setEncoder(new PatternLayoutEncoder() {
{
setContext((Context) LoggerFactory.getILoggerFactory());
setPattern("[%date] %highlight([%level]) [%logger{10} %file:%line] [%thread] %msg%n");
start();
}
});
// 视频分片列表
List<Triple<Integer, Integer, String>> videos = new ArrayList<>();
// HLS索引URL
String playlistUrl = "http://hls.open.ys7.com/openlive/f01018a141094b7fa138b9d0b856507b.hd.m3u8";
// 当前下载的秒数
int seconds = 0;
// 总共下载的秒数
int needSeconds = 20;
loop:
while (true) {
log.info("Requesting playlist: {}", playlistUrl);
// 下载索引文件
String text = restTemplate.getForObject(playlistUrl, String.class);
assert text != null;
/// 获取视频片段时长
Matcher targetDurationMatcher = Pattern.compile("#EXT-X-TARGETDURATION:(\\d+)", Pattern.DOTALL).matcher(text);
assert targetDurationMatcher.find();
int targetDuration = Integer.parseInt(targetDurationMatcher.group(1));
/// 获取视频片段
Pattern pattern = Pattern.compile("#EXT-TS-OFFSET-BEGIN:(?<offset>\\d+)[\r\n]+#EXTINF:(?<duration>[\\d.]+).+?(?<url>http://\\S+)", Pattern.DOTALL);
Matcher matcher = pattern.matcher(text);
while (matcher.find()) {
// 视频片段Offset
int offset = Integer.parseInt(matcher.group("offset"));
// 视频片段Duration
int duration = Math.round(Float.parseFloat(matcher.group("duration")));
// 视频片段URL
String url = matcher.group("url");
Triple<Integer, Integer, String> triple = Triple.of(offset, duration, url);
// 不下载重复文件
if (!videos.contains(triple)) {
log.info("Downloading: {}", url);
// 下载视频片段
FileUtils.copyURLToFile(new URL(url), new File((seconds / targetDuration) + ".ts"));
videos.add(triple);
// 更新下载进度
seconds += targetDuration;
// 下载完毕,退出循环
if (seconds >= needSeconds) {
break loop;
}
} else {
log.info("Ignored: {}", url);
}
}
// 等待一个视频片段的时长,等待索引文件更新
log.info("Sleeping...");
Try.run(() -> Thread.sleep(targetDuration * 1000)).get();
}
videos.forEach(System.out::println);
}
}
控制台输出
[2018-12-04 16:56:48,894] [INFO] [bj.HlsTest HlsTest.java:58] [main] Requesting playlist: http://hls.open.ys7.com/openlive/f01018a141094b7fa138b9d0b856507b.hd.m3u8
[2018-12-04 16:56:49,387] [INFO] [bj.HlsTest HlsTest.java:84] [main] Downloading: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1953.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
[2018-12-04 16:56:49,821] [INFO] [bj.HlsTest HlsTest.java:84] [main] Downloading: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1954.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
[2018-12-04 16:56:50,249] [INFO] [bj.HlsTest HlsTest.java:84] [main] Downloading: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1955.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
[2018-12-04 16:56:50,451] [INFO] [bj.HlsTest HlsTest.java:104] [main] Sleeping...
[2018-12-04 16:56:54,524] [INFO] [bj.HlsTest HlsTest.java:58] [main] Requesting playlist: http://hls.open.ys7.com/openlive/f01018a141094b7fa138b9d0b856507b.hd.m3u8
[2018-12-04 16:56:54,631] [INFO] [bj.HlsTest HlsTest.java:99] [main] Ignored: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1955.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
[2018-12-04 16:56:54,632] [INFO] [bj.HlsTest HlsTest.java:84] [main] Downloading: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1956.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
[2018-12-04 16:56:54,978] [INFO] [bj.HlsTest HlsTest.java:84] [main] Downloading: http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1957.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a
(5859,4,http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1953.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a)
(5862,4,http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1954.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a)
(5865,4,http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1955.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a)
(5868,4,http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1956.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a)
(5871,4,http://hzhls05.ys7.com:7894/openlivedata/203751922_1_1/dc6267715c6243d4a224da0903be9408-1957.ts?Usr=c1cbc1d4e86d49a0981f54beea95280a)