斗鱼直播流下载

This commit is contained in:
Klein
2023-08-05 00:03:13 +08:00
parent 4d4a4a6727
commit 0a8dd6e6ba
6 changed files with 173 additions and 1 deletions

View File

@@ -0,0 +1,33 @@
package org.example;
import org.apache.poi.ss.formula.functions.T;
import org.example.core.manager.TaskManager;
import org.example.pojo.live.DouyuLiveConfig;
public class DouyuLiveTest {
public static void main(String[] args) {
TaskManager taskManager = new TaskManager(5);
DouyuLiveConfig douyuLiveConfig = new DouyuLiveConfig("3637778", "C:\\Users\\admin\\Desktop\\douyu\\", "DouyuLiveOnline");
try {
// 向任务管理器中添加任务
String taskId = taskManager.addTask(douyuLiveConfig); // 获取任务的标识符
int cnt = 10;
while (cnt > 0) {
cnt--;
if (taskManager.isConnectionClosed(taskId)) {
System.out.println("连接中断,已停止录制...");
break;
}
System.out.println("平均下载速度:" + taskManager.getDownloadSpeedAvg(taskId) + " B/s");
System.out.println("瞬时下载速度:" + taskManager.getDownloadSpeed(taskId) + " B/s");
System.out.println("已写入数据量:" + taskManager.getDownloadedBytes(taskId) + " bytes");
Thread.sleep(1000);
}
} catch (Exception e) {
e.printStackTrace();
}
}
}

View File

@@ -3,7 +3,9 @@ package org.example.core.factory;
import org.example.core.parser.PlatformVideoUrlParser;
import org.example.core.parser.impl.BilibiliFlvUrlParser;
import org.example.core.component.LiveStreamTask;
import org.example.core.parser.impl.DouyuFlvUrlParser;
import org.example.pojo.live.BilibiliLiveConfig;
import org.example.pojo.live.DouyuLiveConfig;
import org.example.pojo.live.LiveConfig;
import java.util.HashMap;
@@ -23,6 +25,7 @@ public class LiveTaskFactory {
// 添加更多的解析器
this.parserMap.put(BilibiliLiveConfig.class, new BilibiliFlvUrlParser());
// 如果你有更多的平台,你可以在这里添加它们的解析器
this.parserMap.put(DouyuLiveConfig.class, new DouyuFlvUrlParser());
}
// 根据LiveConfig去创建下载任务
@@ -43,6 +46,13 @@ public class LiveTaskFactory {
headers.put("Referer", "https://live.bilibili.com/");
task.setHeaders(headers);
}
else if (liveConfig instanceof DouyuLiveConfig) {
Map<String, String> headers = new HashMap<>();
headers.put("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/92.0.4515.131 Safari/537.36");
headers.put("Origin", "https://www.douyu.com");
headers.put("Referer", "https://www.douyu.com/topic/15ZNQ?rid=3357246&dyshid=0-818074ef9c05a3fe94acdfe500091601");
task.setHeaders(headers);
}
// 如果有其他平台的直播,可以在这里添加相应的请求头
return task;
} catch (Exception e) {

View File

@@ -0,0 +1,74 @@
package org.example.core.parser.impl;
import org.apache.commons.codec.digest.DigestUtils;
import org.example.core.parser.PlatformVideoUrlParser;
import org.example.pojo.live.DouyuLiveConfig;
import org.example.pojo.live.LiveConfig;
import org.example.utils.HttpClientUtil;
import org.example.utils.RegexUtil;
import org.json.JSONObject;
import javax.script.Invocable;
import javax.script.ScriptEngine;
import javax.script.ScriptEngineManager;
import java.time.LocalDate;
public class DouyuFlvUrlParser implements PlatformVideoUrlParser {
String flvBaseUrl = "http://openflv-huos.douyucdn2.cn/dyliveflv1/";
String did = "818074ef9c05a3fe94acdfe500091601";
@Override
public String getUrl(LiveConfig liveConfig) throws Exception {
DouyuLiveConfig douyuLiveConfig = (DouyuLiveConfig) liveConfig;
String roomId = douyuLiveConfig.getRoomId();
int clarity = douyuLiveConfig.getClarity();
String html = HttpClientUtil.get("https://www.douyu.com/" + roomId);
// 流程: 获取直播间页面
// 从页面内获取一个函数和两个变量(每个直播间不同)
// 该函数会生成一段JavaScript代码用于签名然后执行
// 生成的代码用到了CryPtoJS库进行md5加密所以要在代码执行前将在java中加密好并将原来的部分替换再执行
// 执行后生成一个签名
// 将签名作为参数请求/getH5play接口从响应中获取直播间对应的flv文件名
// 得到链接
String js_fun = RegexUtil.match(html, "(function ub98484234[\\s\\S]*?return eval[\\s\\S]*?);}")[0];
String param_str = js_fun.substring(js_fun.indexOf("(") + 1, js_fun.indexOf(")"));
String[] params = param_str.split(",");
int index = js_fun.indexOf("){");
String js_fun_head = js_fun.substring(0, index + 2);
js_fun = js_fun.substring(index + 2);
String js_var = RegexUtil.match(html, "(var vdwdae325w_64we[\\s\\S]*?;)")[0];
String var2 = RegexUtil.match(js_fun, "v = ([\\s\\S]*).slice[\\s\\S]*?")[0];
String var2_name = var2.substring(4, var2.length() - 6);
String js_var2 = RegexUtil.match(html, "\\b(var " + var2_name + "[\\s\\S]*?;)")[0];
js_fun = js_var + js_var2 + js_fun;
js_fun = js_fun_head + js_fun;
long time = (long) (System.currentTimeMillis() / 1e3);
String cb = roomId + did + time + "2201" + LocalDate.now().format(java.time.format.DateTimeFormatter.ofPattern("yyyyMMdd"));
// cb = "3357246818074ef9c05a3fe94acdfe5000916011691158546220120230804";
String rb = DigestUtils.md5Hex(cb);
String insert = String.format(".replace(\"CryptoJS.MD5(cb).toString()\",\"'%s'\")", rb); //js代码中用到了CryptoJS库md5加密需要先加密好然后替换
int index2 = js_fun.lastIndexOf("strc");
js_fun = js_fun.substring(0, index2 + 4) + insert + js_fun.substring(index2 + 4); //替换
ScriptEngineManager manager = new ScriptEngineManager(); //执行js签名代码
ScriptEngine engine = manager.getEngineByName("js");
// String script = String.format("var ub = new Function('%s','%s','%s','%s')",params[0],params[1],params[2],js_fun);
engine.eval(js_fun);
Invocable inv = (Invocable) engine;
Object ret = inv.invokeFunction("ub98484234", roomId, did, time);
String resp = HttpClientUtil.post(String.format("https://www.douyu.com/lapi/live/getH5Play/%s?%s&cdn=ws-h5&rate=0", roomId, ret));
JSONObject respObj = new JSONObject(resp);
if (respObj.getInt("error") == 0) {
JSONObject dataObj = respObj.getJSONObject("data");
if(dataObj!=null){
String fileUrl = dataObj.getString("rtmp_live");
if(fileUrl!=null){
String name = fileUrl.substring(0,fileUrl.indexOf("."));
return String.format(flvBaseUrl+"%s.flv",name);
}
}
}
throw new Exception("没有找到直播流地址");
}
}

View File

@@ -0,0 +1,20 @@
package org.example.pojo.live;
import lombok.Data;
@Data
public class DouyuLiveConfig extends LiveConfig{
// 清晰度,4000蓝光
private int clarity;
public DouyuLiveConfig(String roomId, int clarity, String videoPath, String videoName, boolean convertToMp4) {
super(roomId, videoPath, videoName, convertToMp4);
this.clarity = clarity;
}
public DouyuLiveConfig(String roomId, String videoPath, String videoName) {
super(roomId, videoPath, videoName, true);
this.clarity = 4000;
}
}

View File

@@ -34,7 +34,9 @@ public class HttpClientUtil {
public static String post(String url, String json) {
return post(url, json, null);
}
public static String post(String url) {
return post2(url, null);
}
// get请求带请求头
public static String get(String url, Map<String, String> headers) {
HttpGet httpGet = new HttpGet(url);
@@ -58,6 +60,16 @@ public class HttpClientUtil {
}
}
public static String post2(String url, Map<String, String> headers) {
HttpPost httpPost = new HttpPost(url);
addHeaders(httpPost, headers);
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
return handleResponse(response);
} catch (IOException e) {
throw new RuntimeException(e);
}
}
// 统一处理请求头
private static void addHeaders(HttpRequest httpRequest, Map<String, String> headers) {
if (headers != null && !headers.isEmpty()) {

View File

@@ -0,0 +1,23 @@
package org.example.utils;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/**
* @author dhx
* @date 2023/8/4 19:36
*/
public class RegexUtil {
public static String[] match(String input, String regex) {
List<String> matches = new ArrayList<>();
Matcher matcher = Pattern.compile(regex).matcher(input);
while (matcher.find()) {
matches.add(matcher.group());
}
return matches.toArray(new String[0]);
}
}