1.导入jar包
<!-- https://mvnrepository.com/artifact/org.bouncycastle/bcprov-jdk16 -->
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk16</artifactId>
<version>1.46</version>
</dependency>
2.工具类
import com.eebbk.notifyservice.exception.M3u8Exception;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.io.*;
import java.net.HttpURLConnection;
import java.net.URL;
import java.util.*;
/**
* @Description m3u8解析
* @project
* @author:hf
* @date:
*/
@Slf4j
public class M3u8DownloadFactory {
//重试次数
private static int retryCount = 30;
//链接连接超时时间(单位:毫秒)
private static long timeoutMillisecond = 1000L;
//密钥字节
private static byte[] keyBytes = new byte[16];
//自定义请求头
private static Map<String, Object> requestHeaderMap = new HashMap<>();
/**
* 获取所有的ts片段下载链接
*
* @return 链接是否被加密,null为非加密
*/
private static Map getTsUrl(String url) {
StringBuilder content = getUrlContent(url, false);
//判断是否是m3u8链接
if (!content.toString().contains("#EXTM3U")){
throw new M3u8Exception(url + "不是m3u8链接!");
}
String[] split = content.toString().split("\\n");
String keyUrl = "";
boolean isKey = false;
for (String s : split) {
//如果含有此字段,则说明只有一层m3u8链接
if (s.contains("#EXT-X-KEY") || s.contains("#EXTINF")) {
isKey = true;
keyUrl = url;
break;
}
}
if (StringUtils.isEmpty(keyUrl)){
throw new M3u8Exception("未发现key链接!");
}
//获取密钥
Map key = isKey ? getKey(keyUrl, content) : getKey(keyUrl, null);
return key;
}
/**
* @Description: 判断是否是为url链接
* @Author: hf
* @Time: 2021/7/13 16:44
*/
public static boolean isUrl(String str) {
if (StringUtils.isEmpty(str)){
return false;
}
str = str.trim();
return str.matches("^(http|https)://.+");
}
/**
* @Description:
* @Return:
* @Author: hf
* @Time: 2021/7/13 16:44
*/
private static String mergeUrl(String start, String end) {
if (end.startsWith("/")){
end = end.replaceFirst("/", "");
}
int position = 0;
String subEnd, tempEnd = end;
while ((position = end.indexOf("/", position)) != -1) {
subEnd = end.substring(0, position + 1);
if (start.endsWith(subEnd)) {
tempEnd = end.replaceFirst(subEnd, "");
break;
}
++position;
}
return start + tempEnd;
}
/**
* @Description:
* @param url 密钥链接,如果无密钥的m3u8,则此字段可为空
* @param content 内容,如果有密钥,则此字段可以为空
* @Return:
* @Author: hf
* @Time: 2021/7/13 16:44
*/
private static Map getKey(String url, StringBuilder content) {
Map<String,String> map=new HashMap<String,String>();
StringBuilder urlContent;
if (content == null || StringUtils.isEmpty(content.toString())){
urlContent = getUrlContent(url, false);
}else{
urlContent = content;
}
if (!urlContent.toString().contains("#EXTM3U")){
throw new M3u8Exception(url + "不是m3u8链接!");
}
String method=null;
String keyUrl=null;
String iv=null;
String[] split = urlContent.toString().split("\\n");
for (String s : split) {
//如果含有此字段,则获取加密算法以及获取密钥的链接
if (s.contains("EXT-X-KEY")) {
String[] split1 = s.split(",");
for (String s1 : split1) {
if (s1.contains("METHOD")) {
method = s1.split("=", 2)[1];
map.put("method",method);
continue;
}
if (s1.contains("URI")) {
keyUrl = s1.split("=", 2)[1];
continue;
}
if (s1.contains("IV")){
iv = s1.split("=", 2)[1];
map.put("iv",iv);
}
}
//只需要 key和vi 直接结束循环
break;
}
}
String aesKey=null;
String relativeUrl = url.substring(0, url.lastIndexOf("/") + 1);
if (!StringUtils.isEmpty(keyUrl)) {
keyUrl = keyUrl.replace("\"", "");
aesKey= getUrlContent(isUrl(keyUrl) ? keyUrl : mergeUrl(relativeUrl, keyUrl), true).toString().replaceAll("\\s+", "");
map.put("aesKey",aesKey);
}
return map;
}
/**
* @Description:
* @param urls http链接
* @param isKey 这个url链接是否用于获取key
* @Return:
* @Author: hf
* @Time: 2021/7/13 16:43
*/
private static StringBuilder getUrlContent(String urls, boolean isKey) {
int count = 1;
HttpURLConnection httpURLConnection = null;
StringBuilder content = new StringBuilder();
while (count <= retryCount) {
try {
URL url = new URL(urls);
httpURLConnection = (HttpURLConnection) url.openConnection();
httpURLConnection.setConnectTimeout((int) timeoutMillisecond);
httpURLConnection.setReadTimeout((int) timeoutMillisecond);
httpURLConnection.setUseCaches(false);
httpURLConnection.setDoInput(true);
for (Map.Entry<String, Object> entry : requestHeaderMap.entrySet()){
httpURLConnection.addRequestProperty(entry.getKey(), entry.getValue().toString());
}
String line;
InputStream inputStream = httpURLConnection.getInputStream();
BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(inputStream));
//这个url链接是否用于获取key
if (isKey) {
byte[] bytes = new byte[128];
int len;
len = inputStream.read(bytes);
if (len == 1 << 4) {
keyBytes = Arrays.copyOf(bytes, 16);
String aesKey= bytesTohex(keyBytes);
content.append(aesKey);
} else{
content.append(new String(Arrays.copyOf(bytes, len)));
}
return content;
}
while ((line = bufferedReader.readLine()) != null){
content.append(line).append("\n");
}
bufferedReader.close();
inputStream.close();
//log.info(content.toString());
break;
} catch (Exception e) {
log.error("第" + count + "获取链接重试!\t" + urls+";"+e.getMessage());
count++;
} finally {
if (httpURLConnection != null) {
httpURLConnection.disconnect();
}
}
}
if (count > retryCount){
throw new M3u8Exception("连接超时!");
}
return content;
}
/**
* @Description: 转16进制
* @Author: hf
* @Time: 2021/7/13 16:35
*/
public static String bytesTohex(byte[] bytes) {
StringBuilder hex = new StringBuilder();
for (int i = 0; i < bytes.length; i++) {
byte b = bytes[i];
boolean flag = false;
if(b < 0){
flag = true;
}
int absB = Math.abs(b);
if(flag){
absB = absB | 0x80;
}
// System.out.println(absB & 0xFF);
String tmp = Integer.toHexString(absB & 0xFF);
//转化的十六进制不足两位,需要补0
if (tmp.length() == 1) {
hex.append("0");
}
hex.append(tmp.toUpperCase());
if(i!=bytes.length-1){
hex.append(" ");
}
}
return hex.toString();
}
public static void main(String[] args) {
String url="xxxx.m3u8";
System.out.println("getTsUrl:"+getTsUrl(url));
}
/**
* @Description: 获取16进制随机数
* @Author: hf
* @Time: 2021/7/13 10:49
*/
public static String randomHexString(int len) {
try {
StringBuffer result = new StringBuffer();
for (int i = 1; i <= len; i++) {
result.append(Integer.toHexString(new Random().nextInt(16)));
if(i%2==0 && i!=len){
result.append(" ");
}
}
return result.toString().toUpperCase();
} catch (Exception e) {
log.error(e.getMessage());
}
return null;
}
}
3.输出加密方式,秘钥,iv