java 多线程m3u8下载

某些时候你需要代理,手机很多电脑比较少,手机分享网络,推荐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 urls = new ArrayList();
		// 获取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 urls) {
		for (int i = 0; i < urls.size(); i++) {
			new File(Dir + "000" + (i + 1) + ".ts").deleteOnExit();
		}
	}

	/**
	 * 合并ts文件
	 * 
	 */
	private static void mergeTs(List 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 urls = new ArrayList();
		// 获取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 urls) {
		for (int i = 0; i < urls.size(); i++) {
			new File(Dir + "000" + (i + 1) + ".ts").delete();
		}
	}

	/**
	 * 合并ts文件
	 * 
	 */
	private static void mergeTs(List 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 urls = new ArrayList();
		// 获取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 urls) {
		for (int i = 0; i < urls.size(); i++) {
			new File(Dir + "000" + (i + 1) + ".ts").delete();
		}
	}

	/**
	 * 合并ts文件
	 * 
	 */
	private static void mergeTs(List 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就行了。 

java 多线程m3u8下载_第1张图片

得到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 urls = new ArrayList();
		// 获取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 urls) {
		for (int i = 0; i < urls.size(); i++) {
			new File(Dir + "000" + (i + 1) + ".ts").delete();
		}
	}

	/**
	 * 合并ts文件
	 * 
	 */
	private static void mergeTs(List 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 urls = new ArrayList();
		// 获取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 urls) {
		for (int i = 0; i < urls.size(); i++) {
			new File(Dir + "000" + (i + 1) + ".ts").delete();
		}
	}

	/**
	 * 合并ts文件
	 * 
	 */
	private static void mergeTs(List 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 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、腾讯会议下载

 java 多线程m3u8下载_第2张图片

 将请求地址复制到代码 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 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 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 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;
        }
    }


}

你可能感兴趣的:(java,java,多线程m3u8下载,m3u8,m3u8多线程下载,Java,m3u8,m3u8下载)