某些时候你需要代理,手机vpn很多电脑vpn比较少,手机分享vpn网络,推荐com.icecoldapps.proxy server。手机电脑需要在同一路由器(或者电脑连手机的热点)。
在代码第一行加上如下代码:
//使用代理服务器
System.getProperties().setProperty("proxySet", "true");
//代理服务器地址
System.getProperties().setProperty("http.proxyHost", "192.168.?.?");
//代理端口
System.getProperties().setProperty("http.proxyPort", "8080");
https参考:
java HTTPS请求绕过证书检测_u014644574的博客-CSDN博客
1、格式1
m3u8下载内容形如:#EXT-X-KEY:METHOD=AES-128,URI=”key.key”
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
public class Test {
static String m3u8url = "https://xx.com.m3u8";// m3u8链接
static String fileName = "";// 保存文件名
static String Dir = "c:/temp/";// 保存路径
static String KEY = "";// 加密视频的密钥,位数必须为16的倍数
static int N = 10;// 线程数10
static int INDEX = 0;// 下标
public static void main(String[] args) throws Exception {
String headUrl = m3u8url.substring(0, m3u8url.lastIndexOf("/") + 1);// 链接的头部
String sendGet = sendGet(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String[] split = sendGet.split("\n");
String url = "";
List<String> urls = new ArrayList<String>();
// 获取ts链接和加密视频的key
for (String s : split) {
if (s.contains("EXT-X-KEY")) {
url = headUrl + s.substring(s.indexOf("URI=") + 5).replace("\"", "");
KEY = sendGet(url, StandardCharsets.UTF_8.name());
System.out.println(KEY);// 加密视频的key
} else if (s.contains(".ts")) {
if (s.startsWith("http")) {
urls.add(s);
} else {
urls.add(headUrl + s);
}
}
}
File f = new File(Dir + "/test.ts");
while (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
// 开启多线程下载
// CountDownLatch countDownLatch = new CountDownLatch(N);// 实例化一个倒计数器,N指定计数个数
// countDownLatch.countDown(); // 计数减一
// countDownLatch.await();// 等待,当计数减到0时,所有线程并行执行
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(N);
for (int i = 0; i < urls.size(); i++) {
fixedThreadPool.execute(() -> {
try {
int index = getIndex();
String ts = sendGet(urls.get(index), StandardCharsets.ISO_8859_1.name());
byte[] tbyte = ts.getBytes(StandardCharsets.ISO_8859_1.name());
if (!"".equals(KEY)) {
tbyte = decryptCBC(tbyte, KEY, KEY);
}
saveFile(tbyte, "000" + (index + 1) + ".ts");
System.out.println("下载000" + (index + 1) + ".ts结束:" + urls.get(index));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
}
fixedThreadPool.shutdown();
// 等待子线程结束,再继续执行下面的代码
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("所有ts下载结束,总共:" + urls.size() + "个文件!");
// 合并ts文件
mergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".mp4");
// 删除ts文件
deleteTs(urls);
}
/**
* 获取其中一个ts文件
*/
private synchronized static int getIndex() {
return INDEX++;
}
/**
* 删除ts文件
*/
private static void deleteTs(List<String> urls) {
for (int i = 0; i < urls.size(); i++) {
new File(Dir + "000" + (i + 1) + ".ts").deleteOnExit();
}
}
/**
* 合并ts文件
*
*/
private static void mergeTs(List<String> urls) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
if ("".equals(fileName)) {
fileName = "1" + new Random().nextInt(10000);
}
File file = new File(Dir + fileName + ".mp4");
fos = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
for (int i = 0; i < urls.size(); i++) {
fis = new FileInputStream(Dir + "000" + (i + 1) + ".ts");
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* AES CBC 解密
* @param key sSrc ts文件字节数组
* @param iv IV,需要和key长度相同
* @return 解密后数据
*/
public static byte[] decryptCBC(byte[] src, String key, String iv) {
try {
byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] content = cipher.doFinal(src);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存ts文件
*/
private static void saveFile(byte[] ts, String name) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Dir + name);
fos.write(ts);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下载文件
*/
public static String sendGet(String url, String charset) {
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
2、格式二
m3u8下载内容形如:#EXT-X-KEY:METHOD=AES-128,URI=”key.key”,IV=0x33336461653165663832316134373162
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//例如:#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x33336461653165663832316134373162
public class Test2 {
static String m3u8url = "https://xx.com.m3u8";// m3u8链接
static String fileName = "";// 保存文件名
static String Dir = "c:/temp/";// 保存路径
static String KEY = "";// 加密视频的密钥,位数必须为16的倍数
static String IV = "";// 加密视频的密钥IV
static int N = 10;// 线程数10
static int INDEX = 0;// 下标
public static void main(String[] args) throws Exception {
String headUrl = m3u8url.substring(0, m3u8url.lastIndexOf("/") + 1);// 链接的头部
String sendGet = sendGet(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String[] split = sendGet.split("\n");
String url = "";
List<String> urls = new ArrayList<String>();
// 获取ts链接和加密视频的key
for (String s : split) {
if (s.contains("EXT-X-KEY")) {
int index = s.indexOf("URI=") + 5;
String keyUrl = s.substring(index, s.indexOf("\"", index));
url = headUrl + keyUrl;
KEY = sendGet(url, StandardCharsets.UTF_8.name());
if (s.contains("IV=")) {
index = s.indexOf("IV=") + 3;
IV = s.substring(index);
if (IV.startsWith("0x")) {
IV = IV.substring(2);
IV = hexStr2Str(IV);
}
}
System.out.println("key:" + KEY);// 加密视频的key
System.out.println("iv:" + IV);// 加密视频的IV
} else if (s.contains(".ts")) {
if (s.startsWith("http")) {
urls.add(s);
} else {
urls.add(headUrl + s);
}
}
}
File f = new File(Dir + "/test.ts");
while (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
// 开启多线程下载
// CountDownLatch countDownLatch = new CountDownLatch(N);// 实例化一个倒计数器,N指定计数个数
// countDownLatch.countDown(); // 计数减一
// countDownLatch.await();// 等待,当计数减到0时,所有线程并行执行
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(N);
for (int i = 0; i < urls.size(); i++) {
fixedThreadPool.execute(() -> {
try {
int index = getIndex();
String ts = sendGet(urls.get(index), StandardCharsets.ISO_8859_1.name());
byte[] tbyte = ts.getBytes(StandardCharsets.ISO_8859_1.name());
if (!"".equals(KEY)) {
tbyte = decryptCBC(tbyte, KEY, IV);
}
saveFile(tbyte, "000" + (index + 1) + ".ts");
System.out.println("下载000" + (index + 1) + ".ts结束:" + urls.get(index));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
}
fixedThreadPool.shutdown();
// 等待子线程结束,再继续执行下面的代码
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("所有ts下载结束,总共:" + urls.size() + "个文件!");
// 合并ts文件
mergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".ts");
// 删除ts文件
deleteTs(urls);
}
/**
* 获取其中一个ts文件
*/
private synchronized static int getIndex() {
return INDEX++;
}
/**
* 删除ts文件
*/
private static void deleteTs(List<String> urls) {
for (int i = 0; i < urls.size(); i++) {
new File(Dir + "000" + (i + 1) + ".ts").delete();
}
}
/**
* 合并ts文件
*
*/
private static void mergeTs(List<String> urls) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
if ("".equals(fileName)) {
fileName = "1" + new Random().nextInt(10000);
}
File file = new File(Dir + fileName + ".ts");
fos = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
for (int i = 0; i < urls.size(); i++) {
fis = new FileInputStream(Dir + "000" + (i + 1) + ".ts");
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* AES CBC 解密
* @param key sSrc ts文件字节数组
* @param iv IV,需要和key长度相同
* @return 解密后数据
*/
public static byte[] decryptCBC(byte[] src, String key, String iv) {
try {
byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] content = cipher.doFinal(src);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存ts文件
*/
private static void saveFile(byte[] ts, String name) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Dir + name);
fos.write(ts);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下载文件
*/
public static String sendGet(String url, String charset) {
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符串转换成为16进制
* @param str
* @return
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
// sb.append(' ');
}
return sb.toString().trim();
}
/**
* 16进制直接转换成为字符串
* @param hexStr
* @return
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
}
3、优化key.key链接错误
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//例如:#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x33336461653165663832316134373162
public class Test3 {
static String m3u8url = "https://xx.com.m3u8";// m3u8链接
static String fileName = "";// 保存文件名
static String Dir = "c:/temp/";// 保存路径
static String KEY = "";// 加密视频的密钥,位数必须为16的倍数
static String IV = "";// 加密视频的密钥IV
static int N = 10;// 线程数10
static int INDEX = 0;// 下标
public static void main(String[] args) throws Exception {
String headUrl = m3u8url.substring(0, m3u8url.lastIndexOf("/") + 1);// 链接的头部
String sendGet = sendGet(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String[] split = sendGet.split("\n");
String url = "";
List<String> urls = new ArrayList<String>();
// 获取ts链接和加密视频的key
for (String s : split) {
if (s.contains("EXT-X-KEY")) {
int index = s.indexOf("URI=") + 5;
String keyUrl = s.substring(index, s.indexOf("\"", index));
url = headUrl + keyUrl;
KEY = sendGet(url, StandardCharsets.UTF_8.name());
if (KEY == null) {// key.key的链接错误)
String replace = keyUrl.substring(0, keyUrl.lastIndexOf("/") + 1);
headUrl = headUrl.replace(replace, "");
url = headUrl + keyUrl;
KEY = sendGet(url, StandardCharsets.UTF_8.name());
}
if (s.contains("IV=")) {
index = s.indexOf("IV=") + 3;
IV = s.substring(index);
if (IV.startsWith("0x")) {
IV = IV.substring(2);
IV = hexStr2Str(IV);
}
} else {
IV = KEY;
}
System.out.println("key:" + KEY);// 加密视频的key
System.out.println("iv:" + IV);// 加密视频的IV
} else if (s.contains(".ts")) {
if (s.startsWith("http")) {
urls.add(s);
} else {
urls.add(headUrl + s);
}
}
}
File f = new File(Dir + "/test.ts");
while (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
// 开启多线程下载
// CountDownLatch countDownLatch = new CountDownLatch(N);// 实例化一个倒计数器,N指定计数个数
// countDownLatch.countDown(); // 计数减一
// countDownLatch.await();// 等待,当计数减到0时,所有线程并行执行
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(N);
for (int i = 0; i < urls.size(); i++) {
fixedThreadPool.execute(() -> {
try {
int index = getIndex();
String ts = sendGet(urls.get(index), StandardCharsets.ISO_8859_1.name());
byte[] tbyte = ts.getBytes(StandardCharsets.ISO_8859_1.name());
if (!"".equals(KEY)) {
tbyte = decryptCBC(tbyte, KEY, IV);
}
saveFile(tbyte, "000" + (index + 1) + ".ts");
System.out.println("下载000" + (index + 1) + ".ts结束:" + urls.get(index));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
}
fixedThreadPool.shutdown();
// 等待子线程结束,再继续执行下面的代码
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("所有ts下载结束,总共:" + urls.size() + "个文件!");
// 合并ts文件
mergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".ts");
// 删除ts文件
deleteTs(urls);
}
/**
* 获取其中一个ts文件
*/
private synchronized static int getIndex() {
return INDEX++;
}
/**
* 删除ts文件
*/
private static void deleteTs(List<String> urls) {
for (int i = 0; i < urls.size(); i++) {
new File(Dir + "000" + (i + 1) + ".ts").delete();
}
}
/**
* 合并ts文件
*
*/
private static void mergeTs(List<String> urls) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
if ("".equals(fileName)) {
fileName = "1" + new Random().nextInt(10000);
}
File file = new File(Dir + fileName + ".ts");
fos = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
for (int i = 0; i < urls.size(); i++) {
fis = new FileInputStream(Dir + "000" + (i + 1) + ".ts");
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* AES CBC 解密
* @param key sSrc ts文件字节数组
* @param iv IV,需要和key长度相同
* @return 解密后数据
*/
public static byte[] decryptCBC(byte[] src, String key, String iv) {
try {
byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] content = cipher.doFinal(src);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存ts文件
*/
private static void saveFile(byte[] ts, String name) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Dir + name);
fos.write(ts);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下载文件
*/
public static String sendGet(String url, String charset) {
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符串转换成为16进制
* @param str
* @return
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
// sb.append(' ');
}
return sb.toString().trim();
}
/**
* 16进制直接转换成为字符串
* @param hexStr
* @return
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
}
4、某腾讯课堂下载,交流学习,请勿用于其他用途
这里为了方便我用“猫抓”,反正可以嗅探m3u8就行了。
得到m3u8下载地址:
https://1.vod2.myqcloud.com/2/3/drm/voddrm.token.v.f30741.m3u8?exper=0&sign=&t=&us=
package test;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.UnsupportedEncodingException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
//例如:#EXT-X-KEY:METHOD=AES-128,URI="https://ke.xx.com/cgi-bin/qcloud/get_dk?xx",IV=0x00000000000000000000000000000000
public class TengXunKeTang {
static String m3u8url = "https://1.vod2.myqcloud.com/2/3/drm/voddrm.token.v.f30741.m3u8?exper=0&sign=&t=&us=";// m3u8链接
static String fileName = "";// 保存文件名
static String Dir = "c:/temp/";// 保存路径
static String KEY = "";// 加密视频的密钥,位数必须为16的倍数
static String IV = "";// 加密视频的密钥IV
static int N = 10;// 线程数10
static int INDEX = 0;// 下标
public static void main(String[] args) throws Exception {
String headUrl = m3u8url.substring(0, m3u8url.lastIndexOf("/") + 1);// 链接的头部
String sendGet = sendGet(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String[] split = sendGet.split("\n");
String url = "";
List<String> urls = new ArrayList<String>();
// 获取ts链接和加密视频的key
for (String s : split) {
if (s.contains("EXT-X-KEY")) {
int index = s.indexOf("URI=") + 5;
String keyUrl = s.substring(index, s.indexOf("\"", index));
if (keyUrl.startsWith("https:") || keyUrl.startsWith("http:")) {
url = keyUrl;
} else {
url = headUrl + keyUrl;
}
if (KEY == null || KEY.length() == 0) {
KEY = sendGet(url, StandardCharsets.ISO_8859_1.name());
System.out.println("key:" + KEY);// 加密视频的key
}
if (IV == null || IV.length() == 0) {
if (s.contains("IV=")) {
index = s.indexOf("IV=") + 3;
IV = s.substring(index);
if (IV.startsWith("0x")) {
IV = IV.substring(2);
IV = hexStr2Str(IV);
}
} else {
IV = KEY;
}
System.out.println("iv:" + IV);// 加密视频的IV
}
} else if (s.contains(".ts")) {
if (s.startsWith("http")) {
urls.add(s);
} else {
urls.add(headUrl + s);
}
}
}
File f = new File(Dir + "/test.ts");
while (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
// 开启多线程下载
// CountDownLatch countDownLatch = new CountDownLatch(N);// 实例化一个倒计数器,N指定计数个数
// countDownLatch.countDown(); // 计数减一
// countDownLatch.await();// 等待,当计数减到0时,所有线程并行执行
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(N);
for (int i = 0; i < urls.size(); i++) {
fixedThreadPool.execute(() -> {
try {
int index = getIndex();
String ts = sendGet(urls.get(index), StandardCharsets.ISO_8859_1.name());
byte[] tbyte = ts.getBytes(StandardCharsets.ISO_8859_1.name());
if (!"".equals(KEY)) {
tbyte = decryptCBC(tbyte, KEY, IV);
}
saveFile(tbyte, "000" + (index + 1) + ".ts");
System.out.println("下载000" + (index + 1) + ".ts结束:" + urls.get(index));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
}
fixedThreadPool.shutdown();
// 等待子线程结束,再继续执行下面的代码
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("所有ts下载结束,总共:" + urls.size() + "个文件!");
// 合并ts文件
mergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".ts");
// 删除ts文件
deleteTs(urls);
}
/**
* 获取其中一个ts文件
*/
private synchronized static int getIndex() {
return INDEX++;
}
/**
* 删除ts文件
*/
private static void deleteTs(List<String> urls) {
for (int i = 0; i < urls.size(); i++) {
new File(Dir + "000" + (i + 1) + ".ts").delete();
}
}
/**
* 合并ts文件
*
*/
private static void mergeTs(List<String> urls) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
if ("".equals(fileName)) {
fileName = "1" + new Random().nextInt(10000);
}
File file = new File(Dir + fileName + ".ts");
fos = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
for (int i = 0; i < urls.size(); i++) {
fis = new FileInputStream(Dir + "000" + (i + 1) + ".ts");
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* AES CBC 解密
* @param key sSrc ts文件字节数组
* @param iv IV,需要和key长度相同
* @return 解密后数据
*/
public static byte[] decryptCBC(byte[] src, String key, String iv) {
try {
byte[] keyByte = key.getBytes(StandardCharsets.ISO_8859_1);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
byte[] ivByte = iv.getBytes(StandardCharsets.ISO_8859_1);
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] content = cipher.doFinal(src);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存ts文件
*/
private static void saveFile(byte[] ts, String name) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Dir + name);
fos.write(ts);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* 下载文件
*/
public static String sendGet(String url, String charset) {
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 字符串转换成为16进制
* @param str
* @return
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
// sb.append(' ');
}
return sb.toString().trim();
}
/**
* 16进制直接转换成为字符串
* @param hexStr
* @return
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
try {
return new String(bytes, "ISO_8859_1");
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return null;
}
}
5、FFmpeg合并ts文件
下载地址:
https://www.gyan.dev/ffmpeg/builds/
下载zip格式,解压。
有时直接合并ts文件,合并出的文件会有跳转卡顿问题,播放卡顿,时间对不上。这时需要使用 ffmpeg 合并。
ffmpeg -i “concat:1.ts|2.ts|3.ts” -c copy output.mp4
package com.demo.test;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import javax.crypto.Cipher;
import javax.crypto.spec.IvParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import javax.net.ssl.HostnameVerifier;
import javax.net.ssl.HttpsURLConnection;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import javax.net.ssl.X509TrustManager;
//例如:#EXT-X-KEY:METHOD=AES-128,URI="key.key",IV=0x33336461653165663832316134373162
public class Test3 {
static String m3u8url = "http://xx.yy.com/cc.m3u8";// m3u8链接
static String fileName = "";// 保存文件名
static String Dir = "c:/temp/";// 保存路径
static String ffmpegDir = "D:/Storages/ffmpeg-5.0-essentials_build/bin/";// ffmpeg解压路径
static String KEY = "";// 加密视频的密钥,位数必须为16的倍数
static String IV = "";// 加密视频的密钥IV
static int N = 10;// 线程数10
static int INDEX = 0;// 下标
public static void main(String[] args) throws Exception {
// 使用代理服务器
// System.getProperties().setProperty("proxySet", "true");
// 代理服务器地址
// System.getProperties().setProperty("http.proxyHost", "192.168.3.4");
// 代理端口
// System.getProperties().setProperty("http.proxyPort", "8080");
String headUrl = m3u8url.substring(0, m3u8url.lastIndexOf("/") + 1);// 链接的头部
if (headUrl.contains("?")) {
headUrl = headUrl.substring(0, headUrl.indexOf("?"));
headUrl = headUrl.substring(0, headUrl.lastIndexOf("/") + 1);// 链接的头部
}
// https下载
// String sendGet = sendGet2(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String sendGet = sendGet(m3u8url, StandardCharsets.UTF_8.name());// 下载index.m3u8
String[] split = sendGet.split("\n");
String url = "";
List<String> urls = new ArrayList<String>();
// 获取ts链接和加密视频的key
for (String s : split) {
if (s.contains("EXT-X-KEY")) {
int index = s.indexOf("URI=") + 5;
String keyUrl = s.substring(index, s.indexOf("\"", index));
if (keyUrl.startsWith("http")) {
url = keyUrl;
} else {
url = headUrl + keyUrl;
}
KEY = sendGet(url, StandardCharsets.UTF_8.name());
if (KEY == null || KEY.length() == 0) {// key.key的链接错误)
String replace = keyUrl.substring(0, keyUrl.lastIndexOf("/") + 1);
headUrl = headUrl.replace(replace, "");
url = headUrl + keyUrl;
KEY = sendGet(url, StandardCharsets.UTF_8.name());
System.out.println("key:" + KEY);// 加密视频的key
}
if (IV == null || IV.length() == 0) {
if (s.contains("IV=")) {
index = s.indexOf("IV=") + 3;
IV = s.substring(index);
if (IV.startsWith("0x")) {
IV = IV.substring(2);
IV = hexStr2Str(IV);
}
} else {
IV = KEY;
}
System.out.println("iv:" + IV);// 加密视频的IV
}
} else if (s.contains(".ts")) {
if (s.startsWith("http")) {
urls.add(s);
} else {
urls.add(headUrl + s);
}
}
}
File f = new File(Dir + "/test.ts");
while (!f.getParentFile().exists()) {
f.getParentFile().mkdirs();
}
// 开启多线程下载
// CountDownLatch countDownLatch = new CountDownLatch(N);// 实例化一个倒计数器,N指定计数个数
// countDownLatch.countDown(); // 计数减一
// countDownLatch.await();// 等待,当计数减到0时,所有线程并行执行
final ExecutorService fixedThreadPool = Executors.newFixedThreadPool(N);
for (int i = 0; i < urls.size(); i++) {
fixedThreadPool.execute(() -> {
try {
int index = getIndex();
String ts = sendGet(urls.get(index), StandardCharsets.ISO_8859_1.name());
byte[] tbyte = ts.getBytes(StandardCharsets.ISO_8859_1.name());
if (!"".equals(KEY)) {
tbyte = decryptCBC(tbyte, KEY, IV);
}
saveFile(tbyte, "000" + (index + 1) + ".ts");
System.out.println("下载000" + (index + 1) + ".ts结束:" + urls.get(index));
} catch (Exception e) {
e.printStackTrace();
} finally {
}
});
}
fixedThreadPool.shutdown();
// 等待子线程结束,再继续执行下面的代码
fixedThreadPool.awaitTermination(Long.MAX_VALUE, TimeUnit.DAYS);
System.out.println("所有ts下载结束,总共:" + urls.size() + "个文件!");
// 合并ts文件
mergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".ts");
// ffmpeg合并ts文件
ffmpegMergeTs(urls);
System.out.println("合并完成:" + Dir + fileName + ".mp4");
// 删除ts文件
deleteTs(urls);
}
/**
* 获取其中一个ts文件
*/
private synchronized static int getIndex() {
return INDEX++;
}
/**
* 删除ts文件
*/
private static void deleteTs(List<String> urls) {
for (int i = 0; i < urls.size(); i++) {
new File(Dir + "000" + (i + 1) + ".ts").delete();
}
}
/**
* 合并ts文件
*
*/
private static void mergeTs(List<String> urls) {
FileOutputStream fos = null;
FileInputStream fis = null;
try {
if ("".equals(fileName)) {
fileName = "1" + new Random().nextInt(10000);
}
File file = new File(Dir + fileName + ".ts");
fos = new FileOutputStream(file);
byte[] buf = new byte[4096];
int len;
for (int i = 0; i < urls.size(); i++) {
fis = new FileInputStream(Dir + "000" + (i + 1) + ".ts");
while ((len = fis.read(buf)) != -1) {
fos.write(buf, 0, len);
}
fos.flush();
fis.close();
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null) {
fis.close();
}
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* ffmpeg合并ts文件
*
*/
private static void ffmpegMergeTs(List<String> urls) {
BufferedWriter bw = null;
try {
File file = new File(Dir + "file.txt");
System.out.println("ffmpeg执行命令:");
String command = ffmpegDir + "ffmpeg.exe -f concat -safe 0 -i " + file.getAbsolutePath() + " -c copy \"" + Dir + fileName + ".mp4\"";
System.out.println(command);
bw = new BufferedWriter(new FileWriter(file));
for (int i = 0; i < urls.size(); i++) {
bw.write("file '" + Dir + "000" + (i + 1) + ".ts" + "'");
bw.newLine();
bw.flush();
}
//
// ffmpeg执行合并命令
//
// ffmpeg -i "concat:1.ts|2.ts|3.ts" -c copy output.mp4
// # -safe 0: 防止Operation not permitted 不允许操作
// ffmpeg.exe -f concat -safe 0 -i file.txt -c copy out.mp4
// ffmpeg -f concat -safe 0 -i file.txt -c copy out.mp4
try {
Process exec = Runtime.getRuntime().exec(command);
exec.getOutputStream().close();
printMessage(exec.getInputStream());
printMessage(exec.getErrorStream());
int value = exec.waitFor();
System.out.println("ffmpeg合并结束:" + value);
} catch (IOException e) {
System.out.println("IO读取异常");
} catch (Exception e) {
System.out.println("程序中断异常");
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (bw != null) {
bw.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* 用于打印日志文件,防止执行阻塞
*/
private static void printMessage(final InputStream input) {
new Thread(() -> {
BufferedReader br = null;
try {
br = new BufferedReader(new InputStreamReader(input, "GBK"));
String line;
while ((line = br.readLine()) != null) {
System.out.println(line);
}
} catch (IOException e) {
System.out.println("IO读取异常");
} finally {
if (br != null) {
try {
br.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}).start();
}
/**
* AES CBC 解密
* @param key sSrc ts文件字节数组
* @param iv IV,需要和key长度相同
* @return 解密后数据
*/
public static byte[] decryptCBC(byte[] src, String key, String iv) {
try {
byte[] keyByte = key.getBytes(StandardCharsets.UTF_8);
SecretKeySpec keySpec = new SecretKeySpec(keyByte, "AES");
byte[] ivByte = iv.getBytes(StandardCharsets.UTF_8);
IvParameterSpec ivSpec = new IvParameterSpec(ivByte);
Cipher cipher = Cipher.getInstance("AES/CBC/PKCS5Padding");
cipher.init(Cipher.DECRYPT_MODE, keySpec, ivSpec);
byte[] content = cipher.doFinal(src);
return content;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
/**
* 保存ts文件
*/
private static void saveFile(byte[] ts, String name) {
FileOutputStream fos = null;
try {
fos = new FileOutputStream(Dir + name);
fos.write(ts);
} catch (Exception e) {
e.printStackTrace();
} finally {
if (fos != null) {
try {
fos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
/**
* http下载文件
*/
public static String sendGet(String url, String charset) {
HttpURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpURLConnection) new URL(url).openConnection();
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
// con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
/**
* https 下载文件
*/
public static String sendGet2(String url, String charset) {
HttpsURLConnection con = null;
InputStream is = null;
ByteArrayOutputStream baos = null;
try {
// 打开连接
con = (HttpsURLConnection) new URL(url).openConnection();
// 绕过证书验证
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[] { new TrustAnyTrustManager() }, new java.security.SecureRandom());
con.setSSLSocketFactory(sc.getSocketFactory());
// 绕过验证主机名和服务器验证方案的匹配是可接受的
con.setHostnameVerifier(new CustomizedHostnameVerifier());
con.setRequestProperty("Content-Type", "application/x-www-form-urlencoded");// 客户端告诉服务器实际发送的数据类型
con.setRequestProperty("Accept", "*/*");
// con.setRequestProperty("Connection", "keep-alive");
con.setRequestProperty("User-Agent", "Mozilla/5.0 (Windows NT 10.0; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/78.0.3904.108 Safari/537.36");
// 开启连接
con.connect();
is = con.getInputStream();
baos = new ByteArrayOutputStream();
byte[] buf = new byte[1024];
int len;
while ((len = is.read(buf)) != -1) {
baos.write(buf, 0, len);
baos.flush();
}
return baos.toString(charset);
} catch (Exception e) {
e.printStackTrace();
return null;
} finally {
try {
if (baos != null) {
baos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
static class TrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType) throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[] {};
}
}
static class CustomizedHostnameVerifier implements HostnameVerifier {
// 重写验证方法
@Override
public boolean verify(String urlHostName, SSLSession session) {
// System.out.println("Warning: URL Host: " + urlHostName + " vs. " + session.getPeerHost());
// 所有都正确
return true;
}
}
/**
* 字符串转换成为16进制
* @param str
* @return
*/
public static String str2HexStr(String str) {
char[] chars = "0123456789ABCDEF".toCharArray();
StringBuilder sb = new StringBuilder("");
byte[] bs = str.getBytes();
int bit;
for (int i = 0; i < bs.length; i++) {
bit = (bs[i] & 0x0f0) >> 4;
sb.append(chars[bit]);
bit = bs[i] & 0x0f;
sb.append(chars[bit]);
// sb.append(' ');
}
return sb.toString().trim();
}
/**
* 16进制直接转换成为字符串
* @param hexStr
* @return
*/
public static String hexStr2Str(String hexStr) {
String str = "0123456789ABCDEF";
char[] hexs = hexStr.toCharArray();
byte[] bytes = new byte[hexStr.length() / 2];
int n;
for (int i = 0; i < bytes.length; i++) {
n = str.indexOf(hexs[2 * i]) * 16;
n += str.indexOf(hexs[2 * i + 1]);
bytes[i] = (byte) (n & 0xff);
}
return new String(bytes);
}
}
6、腾讯会议下载
将请求地址复制到代码 url中
package com.cy.m3u8;
import javax.net.ssl.*;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.URL;
import java.security.cert.CertificateException;
import java.security.cert.X509Certificate;
import java.util.HashMap;
import java.util.Map;
public class TengXunHuiYi {
private static String url = "https://yunluzhi-az-1258344699.file.myqcloud.com/cos/2....-recording-1.mp4?token=....";
private static String path = "F:/two.mp4";
public static void main(String[] args) throws Exception {
Map<String, String> heads = new HashMap<>();
heads.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/108.0.0.0 Safari/537.36");
heads.put("Referer", "https://meeting.tencent.com/");
// heads.put("Range", "bytes=7188865-282279583");
// heads.put("If-Range", "xxxxxxxxxxxxx-3");
String sendGet = sendGet(url, heads, path);
System.out.println(sendGet);
}
/**
* Get请求
*/
public static String sendGet(String url, Map<String, String> heads, String path) {
HttpsURLConnection con = null;
FileOutputStream fos = null;
InputStream is = null;
try {
// 打开连接
con = (HttpsURLConnection) new URL(url).openConnection();
// 绕过证书验证
SSLContext sc = SSLContext.getInstance("SSL");
sc.init(null, new TrustManager[]{new MyTrustAnyTrustManager()}, new java.security.SecureRandom());
con.setSSLSocketFactory(sc.getSocketFactory());
// 绕过验证主机名和服务器验证方案的匹配是可接受的
con.setHostnameVerifier(new CustomizedHostnameVerifier());
//
con.setRequestMethod("GET");
con.setConnectTimeout(10000);//是建立连接的超时时间
con.setReadTimeout(10000);//是传递数据的超时时间
//请求头
// con.setRequestProperty("Accept-Encoding", "gzip, deflate");//千万不要使用这个请求头,因为我们不会去解压。
// con.setRequestProperty("Accept", "application/octet-stream");//二进制流数据
// con.setRequestProperty("Accept", "application/json");
// con.setRequestProperty("Connection", "keep-alive");//抓包发现,不加也是这个
if (heads != null && heads.size() > 0) {
for (Map.Entry<String, String> entrySet : heads.entrySet()) {
con.setRequestProperty(entrySet.getKey(), entrySet.getValue());
}
}
// 开启连接
con.connect();
System.out.println(con.getResponseCode());
if (con.getResponseCode() == 200) {
is = con.getInputStream();
byte[] buf = new byte[1024];
int len;
fos = new FileOutputStream(path);
while ((len = is.read(buf)) != -1) {
fos.write(buf, 0, len);
fos.flush();
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
if (is != null) {
is.close();
}
if (con != null) {
con.disconnect();
}
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
/**
* 受信任的客户端处理
* 自定义X509TrustManager中的checkClientTreusted和checkServerTrusted都是空实现,也就是不检查服务端的SSL证书信息。
* @date 2022/8/10 9:36
*/
static class MyTrustAnyTrustManager implements X509TrustManager {
public void checkClientTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public void checkServerTrusted(X509Certificate[] chain, String authType)
throws CertificateException {
}
public X509Certificate[] getAcceptedIssuers() {
return new X509Certificate[]{};
}
}
/**
* host验证
* 自定义HostnameVerifier中的verify方法返回true,默认信任所有域名,否则在请求时会报错。
* @date 2022/8/10 9:37
*/
static class CustomizedHostnameVerifier implements HostnameVerifier {
// 重写验证方法
@Override
public boolean verify(String urlHostName, SSLSession session) {
// 所有都正确
return true;
}
}
}