update 调整弹幕按批次加载,防止过多

This commit is contained in:
zlzw 2024-11-28 17:54:26 +08:00
parent 903ba44bbb
commit 4474da490f
16 changed files with 289 additions and 150 deletions

View File

@ -1,5 +1,6 @@
<!DOCTYPE html> <!DOCTYPE html>
<html lang="zh-CN"> <html lang="zh-CN">
<head> <head>
<meta charset="utf-8"> <meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
@ -11,15 +12,56 @@
<div id="header"></div> <div id="header"></div>
不想做了,鸽 不想做了,鸽
</body> </body>
<script src="/layui/layui.js"></script>
<script src="/js/jquery-3.2.1.js"></script> <script src="/js/jquery-3.2.1.js"></script>
<script src="/js/CommonConfig.js"></script> <script src="/js/CommonConfig.js"></script>
<script src="/js/httpUtils.js"></script>
<script src="/layui/layui.js"></script>
<script src="/js/lodash.min.js"></script>
<script src="/js/Chart.min.js"></script>
<script> <script>
headerModel=3; headerModel = 3;
var roomId = getParam('roomId');
$('#header').load("/html/header.html"); $('#header').load("/html/header.html");
</script> </script>
<script>
function init() {
if (!hasString(getParam('roomId'))) {
showAnchor();
return
}
initTabs()
initBarrageRenderer();
}
function showAnchor() {
layer.open({
type: 2, // page 层类型
area: ['320px', '400px'],
title: '选择主播',
shade: 0.6, // 遮罩透明度
shadeClose: false, // 点击遮罩区域,关闭弹层
maxmin: true, // 允许全屏最小化
anim: 1, // 0-6 的动画形式,-1 不开启
btn: ['确定', '取消'],
content: '/html/ui/selectAnchor.html',
yes: function (index, layero) {
var iframeWin = window[layero.find('iframe')[0]['name']];
var anchorRoomId = iframeWin.$('#anchorRoomId')[0].value;
if (!hasString(anchorRoomId)) {
layer.msg("请选择")
return
}
let url = new URL(window.location.href);
url.searchParams.set('roomId', anchorRoomId);
window.location.href = url.toString();
}
});
}
init();
</script>
<style> <style>
</style> </style>
</html> </html>

View File

@ -91,10 +91,10 @@
{ field: 'recordPath', title: '保存路径', width: 120 }, { field: 'recordPath', title: '保存路径', width: 120 },
{ field: 'recordDanmu', title: '录制弹幕', width: 120, sort: true }, { field: 'recordDanmu', title: '录制弹幕', width: 120, sort: true },
{ field: 'recordLive', title: '录制视频', width: 120, sort: true }, { field: 'recordLive', title: '录制视频', width: 120, sort: true },
{ field: 'syncDanmuForLive', title: '同步录制', width: 120, sort: true },
{ field: 'recordDanmuDate', title: '弹幕录制预定时间', width: 100, sort: true }, { field: 'recordDanmuDate', title: '弹幕录制预定时间', width: 100, sort: true },
{ field: 'recordLiveDate', title: '视频录制预定时间', width: 100, sort: true }, { field: 'recordLiveDate', title: '视频录制预定时间', width: 100, sort: true },
{ field: 'recordUid', title: '录制账号', width: 120, sort: true, templet: '<div><a href="https://space.bilibili.com/{{= d.recordUid}}" target="_blank">{{= d.recordUid}}</a>' }, { field: 'recordUid', title: '录制账号', width: 120, sort: true, templet: '<div><a href="https://space.bilibili.com/{{= d.recordUid}}" target="_blank">{{= d.recordUid}}</a>' },
{ field: 'recordDanmu', title: '录制弹幕', width: 120, sort: true },
{ field: 'sql_time', title: '添加时间', width: 100 }, { field: 'sql_time', title: '添加时间', width: 100 },
{ fixed: "right", title: "操作", width: 190, align: "center", toolbar: "#toolDemo" } { fixed: "right", title: "操作", width: 190, align: "center", toolbar: "#toolDemo" }
]], ]],

View File

@ -6,11 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1"> <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>视频中心</title> <title>视频中心</title>
<link rel="stylesheet" href="/layui/css/layui.css"> <link rel="stylesheet" href="/layui/css/layui.css">
<style>
canvas {
width: 600px;
}
</style>
</head> </head>
<body class="layui-bg-gray"> <body class="layui-bg-gray">
@ -27,22 +23,17 @@
</div> </div>
</div> </div>
<div class="layui-col-xs7"> <div class="layui-col-xs7">
<div class="layui-row">
<div class="grid-demo" id="container" style="margin-top: 16px; width: 100%;"> <div class="grid-demo" id="container" style="margin-top: 16px; width: 100%;">
<video id="videoElement" controls style="width: 100%;"></video> <video id="videoElement" controls style="width: 100%;"></video>
</div> </div>
</div> </div>
<div class="layui-col-xs2" style="margin-left: 16px; width: 22%; margin-top: 16px;">
<p style="font-size: 4;font-weight: bold; text-align: center;">Super&nbsp;Chat&nbsp;列表</p>
<div class="flow-demo" id="flow" style="height: 60vh; overflow: auto;"></div>
</div>
</div>
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs2">&nbsp;</div>
<div class="layui-form layui-col-xs1"> <div class="layui-form layui-col-xs1">
<input type="checkbox" id="danmuCheckBox" title="弹幕" lay-skin="tag" lay-filter="danmuCheckBox" on checked> <input type="checkbox" id="danmuCheckBox" title="弹幕" lay-skin="tag" lay-filter="danmuCheckBox" on
checked>
</div> </div>
<div class="layui-col-xs9" style="text-align: center;">
<div class="layui-col-xs4" style="text-align: center;">
&nbsp; <div id="slider" lay-options="{value: 100,input:true}"></div> &nbsp; <div id="slider" lay-options="{value: 100,input:true}"></div>
<span>弹幕透明度</span> <span>弹幕透明度</span>
</div> </div>
@ -50,13 +41,26 @@
弹幕装载数:<span id="danmuSize">你猜</span> 弹幕装载数:<span id="danmuSize">你猜</span>
</div> </div>
</div> </div>
</div>
<div class="layui-col-xs2" style="margin-left: 16px; width: 22%; margin-top: 16px;">
<div class="layui-row"> <div class="layui-row">
<div class="layui-col-xs2">&nbsp;</div> <div class="layui-col-md12">
<div class="layui-col-xs7" style="width: auto; height: 50%;"> <canvas id="giftChart" style="width: 100%; max-height: 100%;"></canvas>
<canvas id="giftChart" style="width: 100%; height: 100%;"></canvas>
</div> </div>
</div> </div>
<div style="margin-bottom: 10vh;"></div> <div class="layui-row">
<div class="layui-col-md12">
<p style="font-size: 4;font-weight: bold; text-align: center;">Super&nbsp;Chat&nbsp;列表</p>
<div class="flow-demo" id="flow" style="max-height: 60vh; overflow: auto;"></div>
</div>
</div>
</div>
</div>
<!-- <div style="margin-bottom: 10vh;"></div> -->
</body> </body>
<script id="menulist" type="text/html"> <script id="menulist" type="text/html">
<ul class="layui-tab-title"> <ul class="layui-tab-title">
@ -252,7 +256,7 @@
lables.push(item.gift_name + "\n" + item.total_price / 100 + "¥") lables.push(item.gift_name + "\n" + item.total_price / 100 + "¥")
values.push(item.total_gift_num) values.push(item.total_gift_num)
}); });
if(chartView!==null){ if (chartView !== null) {
chartView.destroy() chartView.destroy()
} }
chartView = new Chart($('#giftChart').get(0), { chartView = new Chart($('#giftChart').get(0), {
@ -332,13 +336,23 @@
} }
function initDanmu() { function initDanmu() {
barrageRenderer.setBarrages([]) barrageRenderer.setBarrages([])
getDanmu(getParam('roomId'), videoId) loadDanmu(0)
}
function loadDanmu(page) {
getDanmu(getParam('roomId'), videoId, page)
.then(data => { .then(data => {
barrageRenderer.setBarrages(data.data.danmu); if (page === 0) {
$('#danmuSize').get(0).innerHTML = data.data.danmu.length
initSC(data.data.superChat) initSC(data.data.superChat)
$('#danmuSize').get(0).innerHTML = data.data.danmuCount;
barrageRenderer.setBarrages(data.data.danmu)
}else{
barrageRenderer.barrageLayoutCalculate.allBarrageInstances.push(...data.data.danmu);
}
if(data.data.nextDanmu){
loadDanmu(page+1)
}
// barrageRenderer.setBarrages(data.data.danmu);
}) })
} }
function initSC(scList) { function initSC(scList) {
flow.load({ flow.load({
@ -347,10 +361,7 @@
isAuto: true, isAuto: true,
isLazyimg: false, isLazyimg: false,
done: function (page, next) { // 加载下一页 done: function (page, next) { // 加载下一页
// 模拟插入
console.log(scList)
laytpl($('#superChatCard').get(0).innerHTML).render(scList, function (html) { laytpl($('#superChatCard').get(0).innerHTML).render(scList, function (html) {
console.log(html)
next(html, false) next(html, false)
}); });
} }

View File

@ -5,6 +5,7 @@
<li class="layui-nav-item video"><a href="/html/body/video.html">视频管理</a></li> <li class="layui-nav-item video"><a href="/html/body/video.html">视频管理</a></li>
<li class="layui-nav-item data" ><a href="/html/body/data.html">数据中心</a></li> <li class="layui-nav-item data" ><a href="/html/body/data.html">数据中心</a></li>
<li class="layui-nav-item user"><a href="/html/body/user.html">用户中心</a></li> <li class="layui-nav-item user"><a href="/html/body/user.html">用户中心</a></li>
<li class="layui-nav-item config layui-layout-right"> <a href="/html/body/config.html"><i class="layui-icon layui-icon-set-fill" style="font-size: 20px; margin-right: 10px;"></i>系统设置</a></li>
</ul> </ul>
</body> </body>
<!-- <script src="/layui/layui.js"></script> --> <!-- <script src="/layui/layui.js"></script> -->
@ -15,11 +16,13 @@
$(".video").removeClass("layui-this") $(".video").removeClass("layui-this")
$(".data").removeClass("layui-this") $(".data").removeClass("layui-this")
$(".user").removeClass("layui-this") $(".user").removeClass("layui-this")
$(".config").removeClass("layui-this")
switch(headerModel){ switch(headerModel){
case 0:$(".index").addClass('layui-this');break; case 0:$(".index").addClass('layui-this');break;
case 1:$(".live").addClass('layui-this');break; case 1:$(".live").addClass('layui-this');break;
case 2:$(".video").addClass('layui-this');break; case 2:$(".video").addClass('layui-this');break;
case 3:$(".data").addClass('layui-this');break; case 3:$(".data").addClass('layui-this');break;
case 4:$(".user").addClass('layui-this');break; case 4:$(".user").addClass('layui-this');break;
case 5:$(".config").addClass('layui-this');break;
} }
</script> </script>

View File

@ -29,6 +29,16 @@
<input type="checkbox" name="recordLive" lay-skin="switch" lay-filter="switchTest" title="启用|禁用"> <input type="checkbox" name="recordLive" lay-skin="switch" lay-filter="switchTest" title="启用|禁用">
</div> </div>
</div> </div>
<div class="layui-form-item" pane>
<label class="layui-form-label">录制视频时<br>同步录制弹幕</label>
<div class="layui-input-block"><br>
<input type="checkbox" name="syncDanmuForLive" lay-skin="switch" lay-filter="switchSync" title="启用|禁用">
<i class="layui-icon layui-icon-help layui-text-em " onclick="timeTips2(this)"></i>
<br>
<br>
</div>
</div>
<div class="layui-form-item" pane> <div class="layui-form-item" pane>
<label class="layui-form-label">录制日期</label> <label class="layui-form-label">录制日期</label>
<div class="layui-input-block"> <div class="layui-input-block">
@ -96,6 +106,10 @@
layer.tips('仅在当前时间范围内录制,如遇到正在直播,则延迟到下播时停止录制', that); layer.tips('仅在当前时间范围内录制,如遇到正在直播,则延迟到下播时停止录制', that);
} }
}
function timeTips2(that) {
layer.tips('启用后,录制视频时会同步录制弹幕,下播后会同步停止录制.同时上面录制弹幕按钮将失效', that);
} }
var roomId = getParam("roomId"); var roomId = getParam("roomId");
var editArray = getParam("array") var editArray = getParam("array")
@ -125,7 +139,7 @@
field.weeks = weeks; field.weeks = weeks;
console.log(field) console.log(field)
if (editArray === null) { if (editArray === null) {
var loadIndex=showLoadingDialog(); var loadIndex = showLoadingDialog();
addRoomConfig(field) addRoomConfig(field)
.then(json => { .then(json => {
layer.close(loadIndex) layer.close(loadIndex)
@ -141,7 +155,7 @@
editArray.split(',').forEach(element => { editArray.split(',').forEach(element => {
array.push(element) array.push(element)
}); });
var loadIndex=showLoadingDialog(); var loadIndex = showLoadingDialog();
setArrayRoomConfig(array, field) setArrayRoomConfig(array, field)
.then(json => { .then(json => {
layer.close(loadIndex) layer.close(loadIndex)
@ -170,6 +184,14 @@
type: 'time', type: 'time',
range: true range: true
}); });
form.on('switch(switchSync)', function (data) {
if (form.val('form-filter').syncDanmuForLive === 'on') {
$("[name='recordDanmu']").prop("disabled", true);
} else {
$("[name='recordDanmu']").prop("disabled", false);
}
form.render();
});
function init() { function init() {
getUserInfo(); getUserInfo();
if (roomId !== null) { if (roomId !== null) {
@ -202,13 +224,18 @@
'recordDanmu': json.recordDanmu, 'recordDanmu': json.recordDanmu,
'recordDanmuDate': json.recordDanmuDate, 'recordDanmuDate': json.recordDanmuDate,
'recordLiveDate': json.recordLiveDate, 'recordLiveDate': json.recordLiveDate,
'syncDanmuForLive': json.syncDanmuForLive,
}; };
for (let i = 0; i < json.weeks.length; i++) { for (let i = 0; i < json.weeks.length; i++) {
result[`week_${json.weeks[i]}`] = true; result[`week_${json.weeks[i]}`] = true;
} }
form.val('form-filter', result); form.val('form-filter', result);
if (json.syncDanmuForLive) {
$("[name='recordDanmu']").prop("disabled", true);
} else {
$("[name='recordDanmu']").prop("disabled", false);
}
$('#url').get(0).disabled = true; $('#url').get(0).disabled = true;
} }
}) })

View File

@ -14,7 +14,7 @@ function get(url) {
}); });
} }
function post(url, formData, isJSON) { function post(url, formData, isJSON) {
return sendPost(url,true,formData,isJSON) return sendPost(url, true, formData, isJSON)
} }
function sendPost(url, isFormData, formData, isJSON) { function sendPost(url, isFormData, formData, isJSON) {
var obj = {}; var obj = {};
@ -129,7 +129,7 @@ function setArrayRoomConfig(rooms, config) {
return sendPost("/live/config/set/array", false, json, true) return sendPost("/live/config/set/array", false, json, true)
} }
function deleteArrayRoomConfig(rooms) { function deleteArrayRoomConfig(rooms) {
return sendPost("/live/config/delete/array", false,rooms, true) return sendPost("/live/config/delete/array", false, rooms, true)
} }
function deleteAllRoomConfig() { function deleteAllRoomConfig() {
return get("/live/config/delete/all") return get("/live/config/delete/all")
@ -192,8 +192,8 @@ function startLiveDanmu(roomId) {
function stopLiveDanmu(roomId) { function stopLiveDanmu(roomId) {
return get("/live/danmu/stop?roomId=" + roomId) return get("/live/danmu/stop?roomId=" + roomId)
} }
function getDanmu(roomId, videoId) { function getDanmu(roomId, videoId, page) {
return get("/live/danmu/get?roomId=" + roomId + "&videoId=" + videoId) return get("/live/danmu/get?roomId=" + roomId + "&videoId=" + videoId + "&page=" + page)
} }
//----------------弹幕相关接口end //----------------弹幕相关接口end
//----------------用户相关接口 //----------------用户相关接口

View File

@ -34,6 +34,8 @@ public class LiveConfigDatabaseBean extends AbsDatabasesBean {
private boolean isRecordLive; private boolean isRecordLive;
@JSONField(name = "recordDanmu") @JSONField(name = "recordDanmu")
private boolean isRecordDanmu; private boolean isRecordDanmu;
@JSONField(name = "syncDanmuForLive")
private boolean isSyncDanmuForLive;
@JSONField(name = "keyword") @JSONField(name = "keyword")
private List<String> keywordList; private List<String> keywordList;
@JSONField(name = "weeks") @JSONField(name = "weeks")

View File

@ -8,6 +8,7 @@ import lombok.Data;
import lombok.EqualsAndHashCode; import lombok.EqualsAndHashCode;
import java.math.BigInteger; import java.math.BigInteger;
import java.util.Date;
@EqualsAndHashCode(callSuper = true) @EqualsAndHashCode(callSuper = true)
@Data @Data
@ -23,7 +24,7 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean {
@JSONField(name = "color") @JSONField(name = "color")
private String fontColor; private String fontColor;
@JSONField(name = "time") @JSONField(name = "time")
private long time; private Date time;
@JSONField(name = "uid") @JSONField(name = "uid")
private String uid; private String uid;
@JSONField(name = "uname") @JSONField(name = "uname")
@ -40,7 +41,7 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean {
model = danmu.getModel(); model = danmu.getModel();
fontSize = danmu.getFontSize(); fontSize = danmu.getFontSize();
fontColor = danmu.getFontColor(); fontColor = danmu.getFontColor();
time = danmu.getTime(); time = new Date(danmu.getTime());
uid = danmu.getUid(); uid = danmu.getUid();
uname = danmu.getUname(); uname = danmu.getUname();
} }
@ -52,7 +53,7 @@ public class LiveDanmuDatabaseBean extends AbsDatabasesBean {
data.setModel(model); data.setModel(model);
data.setFontSize(fontSize); data.setFontSize(fontSize);
data.setFontColor(fontColor); data.setFontColor(fontColor);
data.setTime(time); data.setTime(time.getTime());
return data; return data;
} }
} }

View File

@ -207,9 +207,39 @@ public class BiliLiveDatabase extends SQLiteManager {
return super.get(tableName, where, clazz); return super.get(tableName, where, clazz);
} }
public <T extends AbsDatabasesBean> List<T> getOfTime(Long startTime, Long endTime, int page,int pageSize, Class<T> clazz) {
String tableName = null;
StringBuilder sb = new StringBuilder();
String where = null;
if (startTime != null) {
sb.append(" `sql_time` >= ").append("\"").append(startTime).append("\"");
}
if (endTime != null) {
if (!sb.isEmpty()) {
sb.append(" and ");
}
sb.append(" `sql_time` <= ").append("\"").append(endTime).append("\"");
}
sb.append("ORDER BY `sql_time` LIMIT ").append(page).append("*").append(pageSize).append(",").append(pageSize);
if (!sb.isEmpty()) {
where = sb.toString();
}
for (AbsDatabasesBean bean : getDataBean()) {
if (bean.getClass() == clazz) {
tableName = bean.getTableName();
break;
}
}
return super.get(tableName, where, clazz);
}
@Override
public int getCount(String table) {
return super.getCount(table);
}
public LiveVideoDatabaseBean getVideo(String videoId) { public LiveVideoDatabaseBean getVideo(String videoId) {
for (LiveVideoDatabaseBean info : getLiveInfos()) { for (LiveVideoDatabaseBean info : getLiveInfos()) {
System.out.println(videoId + " " + info.getSql_time().getTime());
if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) { if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) {
return info; return info;
} }
@ -241,8 +271,8 @@ public class BiliLiveDatabase extends SQLiteManager {
} }
public static void main(String[] args) { public static void main(String[] args) {
BiliLiveDatabase biliLiveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig("7688602")); BiliLiveDatabase biliLiveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig("33989"));
biliLiveDatabase.resetSQL(); // biliLiveDatabase.resetSQL();
biliLiveDatabase.resetData(); biliLiveDatabase.resetData();
} }

View File

@ -16,7 +16,6 @@ import com.yutou.biliapi.bean.websocket.WebSocketHeader;
import com.yutou.biliapi.bean.websocket.live.WSData; import com.yutou.biliapi.bean.websocket.live.WSData;
import com.yutou.biliapi.databases.BiliBiliLoginDatabase; import com.yutou.biliapi.databases.BiliBiliLoginDatabase;
import com.yutou.biliapi.databases.BiliLiveDatabase; import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.biliapi.utils.BiliUserUtils;
import com.yutou.biliapi.utils.BytesUtils; import com.yutou.biliapi.utils.BytesUtils;
import com.yutou.common.okhttp.HttpBody; import com.yutou.common.okhttp.HttpBody;
import com.yutou.common.okhttp.HttpCallback; import com.yutou.common.okhttp.HttpCallback;
@ -39,20 +38,20 @@ import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
public class WebSocketManager { public class WebSocketClientManager {
ThreadPoolExecutor executor; ThreadPoolExecutor executor;
private static WebSocketManager instance; private static WebSocketClientManager instance;
Map<LiveRoomConfig, DanmuTask> roomMap; Map<LiveRoomConfig, DanmuTask> roomMap;
private final List<String> userStopList = new ArrayList<>();//手动停止列表 private final List<String> userStopList = new ArrayList<>();//手动停止列表
private WebSocketManager() { private WebSocketClientManager() {
roomMap = new HashMap<>(); roomMap = new HashMap<>();
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100)); executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
} }
public static WebSocketManager getInstance() { public static WebSocketClientManager getInstance() {
if (instance == null) { if (instance == null) {
instance = new WebSocketManager(); instance = new WebSocketClientManager();
} }
return instance; return instance;
} }
@ -105,7 +104,7 @@ public class WebSocketManager {
public DanmuTask(LiveRoomConfig config) { public DanmuTask(LiveRoomConfig config) {
this.roomConfig = config; this.roomConfig = config;
WebSocketManager.getInstance().roomMap.put(roomConfig, this); WebSocketClientManager.getInstance().roomMap.put(roomConfig, this);
} }
@Override @Override
@ -118,7 +117,7 @@ public class WebSocketManager {
roomConfig.setRoomInfo(execute.body() != null ? execute.body().getData() : null); roomConfig.setRoomInfo(execute.body() != null ? execute.body().getData() : null);
} }
} catch (IOException e) { } catch (IOException e) {
WebSocketManager.getInstance().roomMap.remove(roomConfig); WebSocketClientManager.getInstance().roomMap.remove(roomConfig);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getRoomId())).enqueue(new HttpCallback<LiveDanmuInfo>() { api.getLiveRoomDanmuInfo(String.valueOf(roomConfig.getRoomId())).enqueue(new HttpCallback<LiveDanmuInfo>() {
@ -132,7 +131,7 @@ public class WebSocketManager {
roomConfig.setLiveInfo(response); roomConfig.setLiveInfo(response);
client = new WebSocketClientTh(new URI(url), roomConfig); client = new WebSocketClientTh(new URI(url), roomConfig);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
WebSocketManager.getInstance().roomMap.remove(roomConfig); WebSocketClientManager.getInstance().roomMap.remove(roomConfig);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@ -140,7 +139,7 @@ public class WebSocketManager {
@Override @Override
public void onFailure(Throwable throwable) { public void onFailure(Throwable throwable) {
WebSocketManager.getInstance().roomMap.remove(roomConfig); WebSocketClientManager.getInstance().roomMap.remove(roomConfig);
Log.e(throwable); Log.e(throwable);
} }
}); });
@ -152,10 +151,9 @@ public class WebSocketManager {
} }
private static class WebSocketClientTh extends WebSocketClient { private static class WebSocketClientTh extends WebSocketClient {
private LiveRoomConfig roomConfig; private final LiveRoomConfig roomConfig;
private HeartbeatTask heartbeatTask; private final HeartbeatTask heartbeatTask;
BiliLiveDatabase liveDatabase; BiliLiveDatabase liveDatabase;
private boolean itTmp = true;
public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId) { public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId) {
super(serverUri); super(serverUri);
@ -192,7 +190,7 @@ public class WebSocketManager {
@Override @Override
public void onClose(int i, String s, boolean b) { public void onClose(int i, String s, boolean b) {
Log.e("WebSocketClientTh.onClose", "i = " + i + ", s = " + s + ", b = " + b, roomConfig.getRoomId(), heartbeatTask.socket.isOpen()); Log.e("WebSocketClientTh.onClose", "i = " + i + ", s = " + s + ", b = " + b, roomConfig.getRoomId(), heartbeatTask.socket.isOpen());
WebSocketManager.getInstance().roomMap.remove(roomConfig); WebSocketClientManager.getInstance().roomMap.remove(roomConfig);
liveDatabase.close(); liveDatabase.close();
heartbeatTask.cancel(); heartbeatTask.cancel();
} }
@ -201,7 +199,7 @@ public class WebSocketManager {
public void onError(Exception e) { public void onError(Exception e) {
Log.i("WebSocketClientTh.onError", roomConfig.getRoomId()); Log.i("WebSocketClientTh.onError", roomConfig.getRoomId());
Log.e(e); Log.e(e);
WebSocketManager.getInstance().roomMap.remove(roomConfig); WebSocketClientManager.getInstance().roomMap.remove(roomConfig);
liveDatabase.close(); liveDatabase.close();
heartbeatTask.cancel(); heartbeatTask.cancel();
} }
@ -285,12 +283,9 @@ public class WebSocketManager {
} }
LoginCookieDatabaseBean cookie = BiliBiliLoginDatabase.getInstance().getCookie(roomConfig.getLoginUid()); LoginCookieDatabaseBean cookie = BiliBiliLoginDatabase.getInstance().getCookie(roomConfig.getLoginUid());
Log.d("cookie:", cookie, "RoomId:" + roomConfig); Log.d("cookie:", cookie, "RoomId:" + roomConfig);
String buvid = BiliUserUtils.getBuvid(cookie);
if (buvid != null) {
try { try {
json.put("roomid", new BigInteger(roomConfig.getRoomId())); json.put("roomid", new BigInteger(roomConfig.getRoomId()));
json.put("protover", 3); json.put("protover", 3);
json.put("buvid", buvid);
json.put("platform", "web"); json.put("platform", "web");
json.put("type", 2); json.put("type", 2);
json.put("key", roomConfig.getLiveInfo().getToken()); json.put("key", roomConfig.getLiveInfo().getToken());
@ -305,11 +300,9 @@ public class WebSocketManager {
// BytesUtils.printHex(outputStream.toByteArray()); // BytesUtils.printHex(outputStream.toByteArray());
Log.i(socket.isOpen(), json.toString()); Log.i(socket.isOpen(), json.toString());
socket.send(outputStream.toByteArray()); socket.send(outputStream.toByteArray());
} catch (Exception e) { } catch (Exception e) {
Log.e(e); Log.e(e);
} }
}
} }

View File

@ -14,7 +14,6 @@ import com.yutou.bilibili.services.LiveConfigService;
import com.yutou.bilibili.services.LiveLoginService; import com.yutou.bilibili.services.LiveLoginService;
import com.yutou.bilibili.services.LiveUserService; import com.yutou.bilibili.services.LiveUserService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import jakarta.servlet.http.HttpServletRequest;
import org.springframework.core.io.FileSystemResource; import org.springframework.core.io.FileSystemResource;
import org.springframework.http.ResponseEntity; import org.springframework.http.ResponseEntity;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@ -154,6 +153,9 @@ public class LiveConfigController {
if("on".equals(config.getString("recordLive"))){ if("on".equals(config.getString("recordLive"))){
bean.setRecordLive(true); bean.setRecordLive(true);
} }
if("on".equals(config.getString("syncDanmuForLive"))){
bean.setSyncDanmuForLive(true);
}
JSONArray jsonArray = jsonObject.getJSONArray("array"); JSONArray jsonArray = jsonObject.getJSONArray("array");
List<LiveConfigDatabaseBean> list = jsonArray.stream().map(roomId -> configService.addConfig(roomId.toString(), bean)).toList(); List<LiveConfigDatabaseBean> list = jsonArray.stream().map(roomId -> configService.addConfig(roomId.toString(), bean)).toList();
int countNull = list.stream().filter(Objects::isNull).toList().size(); int countNull = list.stream().filter(Objects::isNull).toList().size();

View File

@ -1,8 +1,6 @@
package com.yutou.bilibili.Controllers; package com.yutou.bilibili.Controllers;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.yutou.biliapi.bean.live.LiveRoomConfig;
import com.yutou.biliapi.net.WebSocketManager;
import com.yutou.bilibili.datas.ResultData; import com.yutou.bilibili.datas.ResultData;
import com.yutou.bilibili.datas.ReturnCode; import com.yutou.bilibili.datas.ReturnCode;
import com.yutou.bilibili.services.LiveDanmuService; import com.yutou.bilibili.services.LiveDanmuService;
@ -44,7 +42,7 @@ public class LiveDanmuController {
@ResponseBody @ResponseBody
@RequestMapping("/live/danmu/get") @RequestMapping("/live/danmu/get")
public JSONObject getDanmu(String roomId, String videoId) { public JSONObject getDanmu(String roomId, String videoId, int page) {
return ResultData.success(service.getDanmu(roomId, videoId)); return ResultData.success(service.getDanmu(roomId, videoId, page));
} }
} }

View File

@ -11,7 +11,8 @@ public class LiveVideoDanmu {
List<Danmu> danmu = new ArrayList<>(); List<Danmu> danmu = new ArrayList<>();
List<SuperChat> superChat = new ArrayList<>(); List<SuperChat> superChat = new ArrayList<>();
boolean isNextDanmu = false;
long danmuCount = 0;
@Data @Data
public static class Addition { public static class Addition {
@ -60,7 +61,7 @@ public class LiveVideoDanmu {
this.backgroundBottomColor = bean.getBackgroundBottomColor(); this.backgroundBottomColor = bean.getBackgroundBottomColor();
this.userName = bean.getUserName(); this.userName = bean.getUserName();
this.userAvatar = bean.getUserAvatar(); this.userAvatar = bean.getUserAvatar();
this.userNameColor=bean.getUserNameColor(); this.userNameColor = bean.getUserNameColor();
} }
} }
} }

View File

@ -10,44 +10,44 @@ import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
import com.yutou.biliapi.bean.websocket.live.WSData; import com.yutou.biliapi.bean.websocket.live.WSData;
import com.yutou.biliapi.databases.BiliLiveConfigDatabase; import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
import com.yutou.biliapi.databases.BiliLiveDatabase; import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.biliapi.net.WebSocketManager; import com.yutou.biliapi.net.WebSocketClientManager;
import com.yutou.bilibili.Tools.AssTools; import com.yutou.bilibili.Tools.AssTools;
import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.Tools.Tools; import com.yutou.bilibili.Tools.Tools;
import com.yutou.bilibili.datas.web.LiveVideoDanmu; import com.yutou.bilibili.datas.web.LiveVideoDanmu;
import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import java.io.File; import java.io.File;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.List; import java.util.List;
@Service @Service
public class LiveDanmuService { public class LiveDanmuService {
public void start(String roomId, boolean isUser) { public void start(String roomId, boolean isUser) {
WebSocketManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId), isUser); WebSocketClientManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId), isUser);
}
public void start(LiveConfigDatabaseBean roomId, boolean isUser) {
WebSocketClientManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId.getRoomId()), isUser);
} }
public boolean check(String roomId) { public boolean check(String roomId) {
LiveRoomConfig roomConfig = new LiveRoomConfig(); LiveRoomConfig roomConfig = new LiveRoomConfig();
roomConfig.setRoomId(roomId); roomConfig.setRoomId(roomId);
return WebSocketManager.getInstance().checkRoom(roomConfig); return WebSocketClientManager.getInstance().checkRoom(roomConfig);
} }
public void stop(String roomId, boolean isUser) { public void stop(String roomId, boolean isUser) {
WebSocketManager.getInstance().stopRoom(roomId, isUser); WebSocketClientManager.getInstance().stopRoom(roomId, isUser);
} }
public JSONArray getLiveRoomList() { public JSONArray getLiveRoomList() {
return WebSocketManager.getInstance().getLiveRoomList(); return WebSocketClientManager.getInstance().getLiveRoomList();
} }
public void clearUserList() { public void clearUserList() {
WebSocketManager.getInstance().clearUserStopList(); WebSocketClientManager.getInstance().clearUserStopList();
} }
public List<File> getDanmuFileList(String roomId) { public List<File> getDanmuFileList(String roomId) {
@ -86,21 +86,33 @@ public class LiveDanmuService {
return String.format("%d小时%d分钟%d秒", hours, minutes, finalRemainingSeconds); return String.format("%d小时%d分钟%d秒", hours, minutes, finalRemainingSeconds);
} }
public LiveVideoDanmu getDanmu(String roomId, String videoId) { public LiveVideoDanmu getDanmu(String roomId, String videoId, int page) {
LiveVideoDanmu danmus = new LiveVideoDanmu(); LiveVideoDanmu danmus = new LiveVideoDanmu();
BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId)); BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId));
LiveVideoDatabaseBean videoBean = liveDatabase.getVideo(videoId); LiveVideoDatabaseBean videoBean = liveDatabase.getVideo(videoId);
if (videoBean == null) { if (videoBean == null) {
return new LiveVideoDanmu(); return new LiveVideoDanmu();
} }
long startTime = Long.parseLong(videoId); long startTime = videoBean.getStartTime().getTime();
long endTime = videoBean.getStopTime() == null ? System.currentTimeMillis() : videoBean.getStopTime().getTime(); long endTime = videoBean.getStopTime() == null ? System.currentTimeMillis() : videoBean.getStopTime().getTime();
List<LiveDanmuDatabaseBean> danmuList = liveDatabase.getOfTime(startTime, endTime, LiveDanmuDatabaseBean.class);
List<LiveSuperChatDatabaseBean> superChatList = liveDatabase.getOfTime(startTime, endTime, LiveSuperChatDatabaseBean.class); List<LiveSuperChatDatabaseBean> superChatList = liveDatabase.getOfTime(startTime, endTime, LiveSuperChatDatabaseBean.class);
for (LiveDanmuDatabaseBean bean : danmuList) {
LiveVideoDanmu.Danmu danmu = createDanmu(bean, startTime); long count = liveDatabase.getCount(new LiveDanmuDatabaseBean().getTableName());
danmus.getDanmu().add(danmu); int pageSize = 10000;
int pageCount = (int) Math.ceil((double) count / pageSize);
List<LiveVideoDanmu.Danmu> danmuList = liveDatabase.getOfTime(startTime, endTime, page, pageSize, LiveDanmuDatabaseBean.class)
.stream()
.map(item -> createDanmu(item, startTime))
.filter(item-> item.getTime()>=0)
.toList();
danmus.getDanmu().addAll(danmuList);
danmus.setDanmuCount(count);
if (page < pageCount) {
danmus.setNextDanmu(true);
} }
for (LiveSuperChatDatabaseBean bean : superChatList) { for (LiveSuperChatDatabaseBean bean : superChatList) {
LiveVideoDanmu.SuperChat superChat = new LiveVideoDanmu.SuperChat(startTime, bean); LiveVideoDanmu.SuperChat superChat = new LiveVideoDanmu.SuperChat(startTime, bean);
danmus.getSuperChat().add(superChat); danmus.getSuperChat().add(superChat);
@ -117,7 +129,7 @@ public class LiveDanmuService {
danmu.setText(bean.getDanmu()); danmu.setText(bean.getDanmu());
danmu.setColor("#" + bean.getFontColor()); danmu.setColor("#" + bean.getFontColor());
danmu.setFontSize(bean.getFontSize()); danmu.setFontSize(bean.getFontSize());
danmu.setTime(bean.getTime() - startTime); danmu.setTime(bean.getTime().getTime() - startTime);
if (bean.getModel() < 4) { if (bean.getModel() < 4) {
danmu.setBarrageType(LiveVideoDanmu.Danmu.DANMU_TYPE_SCROLL); danmu.setBarrageType(LiveVideoDanmu.Danmu.DANMU_TYPE_SCROLL);
} else if (bean.getModel() == 4) { } else if (bean.getModel() == 4) {

View File

@ -18,11 +18,9 @@ import com.yutou.biliapi.enums.LiveVideoCodec;
import com.yutou.biliapi.enums.LiveVideoDefinition; import com.yutou.biliapi.enums.LiveVideoDefinition;
import com.yutou.biliapi.enums.LiveVideoFormat; import com.yutou.biliapi.enums.LiveVideoFormat;
import com.yutou.biliapi.net.BiliLiveNetApiManager; import com.yutou.biliapi.net.BiliLiveNetApiManager;
import com.yutou.biliapi.net.WebSignManager; import com.yutou.biliapi.net.WebSocketClientManager;
import com.yutou.bilibili.Tools.DateFormatUtils; import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.Tools.FileServerUtils; import com.yutou.bilibili.Tools.FileServerUtils;
import com.yutou.bilibili.Tools.LiveInfoNfoTools;
import com.yutou.bilibili.Tools.ProcessUtils;
import com.yutou.bilibili.datas.VideoFilePath; import com.yutou.bilibili.datas.VideoFilePath;
import com.yutou.bilibili.interfaces.DownloadInterface; import com.yutou.bilibili.interfaces.DownloadInterface;
import com.yutou.common.okhttp.HttpCallback; import com.yutou.common.okhttp.HttpCallback;
@ -32,13 +30,14 @@ import com.yutou.common.utils.ConfigTools;
import com.yutou.common.utils.FFmpegUtils; import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import okhttp3.Headers; import okhttp3.Headers;
import org.apache.commons.io.FileUtils;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import java.io.File; import java.io.File;
import java.io.IOException;
import java.util.*; import java.util.*;
import java.util.concurrent.ArrayBlockingQueue; import java.util.concurrent.ArrayBlockingQueue;
import java.util.concurrent.Executors;
import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
@ -65,16 +64,16 @@ public class LiveVideoDownloadService {
} }
public void start(LiveConfigDatabaseBean bean, boolean isUser) { public void start(LiveConfigDatabaseBean bean, boolean isUser) {
if (!isUser && userStopList.contains(bean.getRoomId().toString())) { if (!isUser && userStopList.contains(bean.getRoomId())) {
return; return;
} }
if (isUser) { if (isUser) {
userStopList.remove(bean.getRoomId().toString()); userStopList.remove(bean.getRoomId());
} }
if (videoRecord.check(bean.getRoomId().toString())) { if (videoRecord.check(bean.getRoomId())) {
return; return;
} }
BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid()).getRoomInfo(bean.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() { BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid()).getRoomInfo(bean.getRoomId()).enqueue(new HttpCallback<LiveRoomInfo>() {
@Override @Override
public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) { public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
if (response.getLiveStatus() == 1) { if (response.getLiveStatus() == 1) {
@ -178,20 +177,9 @@ public class LiveVideoDownloadService {
config.setRoomInfo(roomInfo); config.setRoomInfo(roomInfo);
config.setRootPath(bean.getRecordPath()); config.setRootPath(bean.getRecordPath());
database = new BiliLiveDatabase(config); database = new BiliLiveDatabase(config);
try {
var keyframe = roomInfo.getKeyframe();
if (!StringUtils.hasText(keyframe)) {
keyframe = roomInfo.getUserCover();
}
HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(keyframe)
.setPath(rootPath.getAbsolutePath())
.setFileName("poster.jpg"));
} catch (Exception e) {
e.printStackTrace();
}
saveLiveInfo(roomInfo); saveLiveInfo(roomInfo);
api.getLiveRoomPlayInfo( api.getLiveRoomPlayInfo(
bean.getRoomId().toString(), bean.getRoomId(),
LiveProtocol.getAll(), LiveProtocol.getAll(),
LiveVideoFormat.getAll(), LiveVideoFormat.getAll(),
LiveVideoCodec.getAll(), LiveVideoCodec.getAll(),
@ -285,7 +273,7 @@ public class LiveVideoDownloadService {
// .withNotSymbolParam("-loglevel", "error") // .withNotSymbolParam("-loglevel", "error")
// .withNotSymbolParam("-progress", "-") // .withNotSymbolParam("-progress", "-")
// .withNotSymbolParam("-fflags", "+genpts") // .withNotSymbolParam("-fflags", "+genpts")
.withNotSymbolParam("-threads", "8") // .withNotSymbolParam("-threads", "8")//看bili-go也没有加这个改成设置好了
// .withNotSymbolParam("-bufsize", "10M") // .withNotSymbolParam("-bufsize", "10M")
.withNotSymbolParam("-f", "segment") .withNotSymbolParam("-f", "segment")
.withNotSymbolParam("-segment_time", "60") .withNotSymbolParam("-segment_time", "60")
@ -301,7 +289,7 @@ public class LiveVideoDownloadService {
if (ck != null) { if (ck != null) {
// builder = builder.withParam("-cookies", cookie); // builder = builder.withParam("-cookies", cookie);
} }
FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath.replace(".m3u8","-%04d.ts")); FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath.replace(".m3u8", "-%04d.ts"));
Log.i(command.getCommandDecode()); Log.i(command.getCommandDecode());
try { try {
command.start(new DownloadInterface() { command.start(new DownloadInterface() {
@ -316,6 +304,7 @@ public class LiveVideoDownloadService {
VideoTask.this.onStart(); VideoTask.this.onStart();
Log.i("启动录制:" + playInfo.getRoomId()); Log.i("启动录制:" + playInfo.getRoomId());
task = null; task = null;
cancel(); cancel();
} }
}; };
@ -333,11 +322,18 @@ public class LiveVideoDownloadService {
if (task != null) { if (task != null) {
task.cancel(); task.cancel();
task = null; task = null;
try {
FileUtils.deleteDirectory(rootPath);
} catch (IOException e) {
Log.i(rootPath.getAbsolutePath());
Log.e(e);
}
} }
if (videoDatabaseBean != null) { if (videoDatabaseBean != null) {
videoDatabaseBean.setStopTime(new Date()); videoDatabaseBean.setStopTime(new Date());
database.addLiveInfo(videoDatabaseBean); database.addLiveInfo(videoDatabaseBean);
} }
stopRecordDanmu();
} }
}); });
@ -353,8 +349,21 @@ public class LiveVideoDownloadService {
videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo)); videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo));
videoDatabaseBean.setStartTime(new Date()); videoDatabaseBean.setStartTime(new Date());
database.addLiveInfo(videoDatabaseBean); database.addLiveInfo(videoDatabaseBean);
recordDanmu();
// LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo")); // LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo"));
}
//录制弹幕
private void recordDanmu() {
if (bean.isSyncDanmuForLive() && !WebSocketClientManager.getInstance().checkRoom(LiveRoomConfig.buildConfig(bean.getRoomId()))) {
WebSocketClientManager.getInstance().addRoom(LiveRoomConfig.buildConfig(bean.getRoomId()), true);
}
}
private void stopRecordDanmu() {
if (bean.isSyncDanmuForLive() && WebSocketClientManager.getInstance().checkRoom(LiveRoomConfig.buildConfig(bean.getRoomId()))) {
WebSocketClientManager.getInstance().stopRoom(bean.getRoomId(), false);
}
} }
} }

View File

@ -54,12 +54,20 @@ public class SystemService {
} }
for (LiveConfigDatabaseBean bean : list) { for (LiveConfigDatabaseBean bean : list) {
try { try {
if (bean.isRecordDanmu() && bean.checkRecordDanmuTime()) { // 如果bean需要录制弹幕并且检查录制弹幕时间并且不需要同步直播弹幕则录制弹幕
if (bean.isRecordDanmu() && bean.checkRecordDanmuTime() && !bean.isSyncDanmuForLive()) {
recordDanmu(bean); recordDanmu(bean);
} else if (!bean.checkRecordDanmuTime()) { } else if (!bean.checkRecordDanmuTime() && !bean.isSyncDanmuForLive()) {
// 如果不在录制弹幕时间并且不需要同步直播弹幕则停止录制弹幕
stopRecordDanmu(bean); stopRecordDanmu(bean);
} }
// 如果bean需要录制直播并且检查录制直播时间
if (bean.isRecordLive() && bean.checkRecordLiveTime()) { if (bean.isRecordLive() && bean.checkRecordLiveTime()) {
// 如果需要同步直播弹幕则录制弹幕
if (bean.isSyncDanmuForLive()) {
recordDanmu(bean);
}
// 录制视频
recordVideo(bean); recordVideo(bean);
} }
} catch (Exception e) { } catch (Exception e) {
@ -83,7 +91,7 @@ public class SystemService {
// 录制弹幕 // 录制弹幕
private void recordDanmu(LiveConfigDatabaseBean bean) { private void recordDanmu(LiveConfigDatabaseBean bean) {
danmuService.start(bean.getRoomId().toString(), false); danmuService.start(bean, false);
} }
// 录制视频 // 录制视频