Compare commits

...

31 Commits

Author SHA1 Message Date
c635d73911 update 日志 2024-12-17 23:39:25 +08:00
516e030266 调整websocket,加线程处理数据,防止阻塞(可能?) 2024-12-10 14:58:12 +08:00
d2bcd5c64a 调整日志 2024-12-10 09:51:22 +08:00
6ee610f4cc 加了日志系统 2024-12-05 10:36:15 +08:00
1803ef845c 调整了数据库缓存的调用,部分使用是直接使用的value,可能无法触发缓存计数 2024-12-02 14:32:39 +08:00
0497e9d0e2 调整websocket断开后重连 2024-12-02 13:54:01 +08:00
e089c57c01 update 2024-11-30 17:45:05 +08:00
b5c85409cf fix字幕颜色不对问题 2024-11-30 17:38:38 +08:00
23861a897f add 加了个打印测试数据入口 2024-11-30 16:37:50 +08:00
313f8ac914 fix 修复添加直播间的sql错误 2024-11-30 15:45:11 +08:00
3ac3c8e5e5 fix 修复添加直播间的sql错误 2024-11-30 15:12:05 +08:00
a12b1b5bd8 fix 修复修改星期失败的问题 2024-11-30 15:03:17 +08:00
dad4bcff15 update 调整数据库
update superchat信息
2024-11-30 14:43:08 +08:00
1bfca68514 fix 补充一个css 2024-11-29 18:16:59 +08:00
6f57c60a75 fix bean.getKeywordList()可能为空 2024-11-29 18:14:04 +08:00
fa8523da97 fix 启用同步录制后,弹幕如果中途异常退出,不会再重新录制问题 2024-11-29 18:04:22 +08:00
e450964cd8 add 新增查看礼物详情列表
fix 调整礼物金额
2024-11-29 17:56:02 +08:00
e4e5696b70 新增了关键词检测
新增了手动暂停的恢复
2024-11-29 15:45:29 +08:00
31dd9cf1a1 update 2024-11-29 11:23:46 +08:00
59a6641d50 定点重置数据改成了定时器方式 2024-11-29 11:01:22 +08:00
5ae1b474f9 调整数据库全部走LiveDatabaseServer 2024-11-29 10:42:47 +08:00
8afb8f8f0b update 统一管理BiliLiveDatabase 2024-11-28 23:18:24 +08:00
3f10e19e50 在获取弹幕时抛异常后尝试关闭数据库 2024-11-28 18:21:50 +08:00
52e4312a32 update 暂时关闭点击录制后刷新页面功能 2024-11-28 18:02:58 +08:00
4474da490f update 调整弹幕按批次加载,防止过多 2024-11-28 17:54:26 +08:00
903ba44bbb 调整UI 2024-11-27 18:17:14 +08:00
38854993d0 完善批量设置 2024-11-27 18:06:15 +08:00
ad84c71a30 更新layui到v2.9.20 2024-11-27 17:40:14 +08:00
e8cceef419 完善web一些功能 2024-11-27 17:39:02 +08:00
48a15c8769 完善切片存储 2024-11-27 17:38:45 +08:00
b5c2153ad0 改成切片保存 2024-11-26 18:28:38 +08:00
58 changed files with 2206 additions and 705 deletions

3
.gitignore vendored
View File

@@ -35,4 +35,5 @@ build/
### dir ### ### dir ###
/live/ /live/
/databases/ /databases/
/cache/ /cache/
/logs/

87
Web/css/inputTag.css Normal file
View File

@@ -0,0 +1,87 @@
@keyframes fariy-fadein {
0% {
opacity: 0;
}
100% {
opacity: 1;
}
}
.fairy-tag-container {
width: auto;
min-height: 100px;
padding: 5px;
border: 1px solid #e6e6e6;
background-color: #ffffff;
}
.fairy-tag-container:hover {
border-color: #d2d2d2;
}
.fairy-tag-container span.fairy-tag {
float: left;
font-size: 13px;
padding: 5px 8px;
margin-right: 5px;
margin-bottom: 5px;
border-radius: 2px;
line-height: 16px;
}
.fairy-tag-container span.fairy-tag a {
font-size: 11px;
font-weight: bolder;
color: #ffffff;
text-decoration: none;
margin-left: 6px;
}
.fairy-tag-container span.fairy-tag a:hover {
cursor: pointer;
}
.fairy-tag-container span.fairy-bg-red {
background-color: #FF5722;
}
.fairy-tag-container span.fairy-bg-orange {
background-color: #FFB800;
}
.fairy-tag-container span.fairy-bg-green {
background-color: #009688;
}
.fairy-tag-container span.fairy-bg-cyan {
background-color: #2F4056;
}
.fairy-tag-container span.fairy-bg-blue {
background-color: #1E9FFF;
}
.fairy-tag-container span.fairy-bg-black {
background-color: #393D49;
}
.fairy-tag-container span.fairy-bg-red,
.fairy-tag-container span.fairy-bg-orange,
.fairy-tag-container span.fairy-bg-green,
.fairy-tag-container span.fairy-bg-cyan,
.fairy-tag-container span.fairy-bg-blue,
.fairy-tag-container span.fairy-bg-black {
color: #ffffff;
}
.fairy-tag-container .fairy-anim-fadein {
animation: fariy-fadein 0.3s both;
}
.fairy-tag-container .fairy-tag-input[type='text'] {
width: 80px;
font-size: 13px;
padding: 6px;
background: transparent;
border: 0 none;
outline: 0;
}
.fairy-tag-container .fairy-tag-input[type='text']:focus::-webkit-input-placeholder {
color: transparent;
}
.fairy-tag-container .fairy-tag-input[type='text']:focus:-moz-placeholder {
color: transparent;
}
.fairy-tag-container .fairy-tag-input[type='text']:focus:-moz-placeholder {
color: transparent;
}
.fairy-tag-container .fairy-tag-input[type='text']:focus:-ms-input-placeholder {
color: transparent;
}
/*# sourceMappingURL=inputTag.css.map */

31
Web/html/body/config.html Normal file
View File

@@ -0,0 +1,31 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1">
<title>系统设置</title>
<link rel="stylesheet" href="/layui/css/layui.css">
</head>
<body>
<div id="header"></div>
不想做了,鸽
</body>
<script src="/js/jquery-3.2.1.js"></script>
<script src="/js/CommonConfig.js"></script>
<script src="/js/httpUtils.js"></script>
<script src="/layui/layui.js"></script>
<script>
headerModel = 5;
$('#header').load("/html/header.html");
</script>
<script>
</script>
<style>
</style>
</html>

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

@@ -15,7 +15,9 @@
<script type="text/html" id="toolbarDemo"> <script type="text/html" id="toolbarDemo">
<div class="layui-btn-container"> <div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" onclick="createRoom()">添加新房间</button> <button class="layui-btn layui-btn-sm" onclick="createRoom()">添加新房间</button>
<button class="layui-btn layui-btn-sm" lay-event="getCheckData">获取选中行数据</button> <button class="layui-btn layui-btn-sm" lay-event="editArray">批量编辑</button>
<button class="layui-btn layui-btn-sm" lay-event="deleteArray">批量删除</button>
<button class="layui-btn layui-btn-sm layui-bg-red" lay-event="deleteAll">全部删除</button>
</div> </div>
</script> </script>
<script type="text/html" id="toolDemo"> <script type="text/html" id="toolDemo">
@@ -41,11 +43,22 @@
layer.open({ layer.open({
type: 2, type: 2,
title: "添加新房间", title: "添加新房间",
maxmin: true,
area: ['600px', '500px'], area: ['600px', '500px'],
content: '/html/ui/createConfig.html?roomId=' + roomId content: '/html/ui/createConfig.html?roomId=' + roomId
}); });
} }
function editArrayRoom(array) {
layer.open({
type: 2,
title: "批量编辑",
maxmin: true,
area: ['600px', '500px'],
content: '/html/ui/createConfig.html?array=' + array
});
}
</script> </script>
<script> <script>
layui.use(['table', 'dropdown'], function () { layui.use(['table', 'dropdown'], function () {
@@ -77,13 +90,13 @@
{ field: 'anchorName', title: '用户名', width: 100, fixed: 'left' }, { field: 'anchorName', title: '用户名', width: 100, fixed: 'left' },
{ field: 'anchorFace', title: '头像', width: 80, templet: '<div><image src="" onerror="showImage(\'{{= d.anchorFace }}\',this);" style="width: 30px;height: 30px;"></div>' }, { field: 'anchorFace', title: '头像', width: 80, templet: '<div><image src="" onerror="showImage(\'{{= d.anchorFace }}\',this);" style="width: 30px;height: 30px;"></div>' },
{ field: 'live_room_id', title: '房间号', width: 80, templet: '<div><a href="https://live.bilibili.com/{{= d.live_room_id}}" target="_blank">{{= d.live_room_id}}</a>' }, { field: 'live_room_id', title: '房间号', width: 80, templet: '<div><a href="https://live.bilibili.com/{{= d.live_room_id}}" target="_blank">{{= d.live_room_id}}</a>' },
{ field: 'recordPath', title: '保存路径', width: 120 }, { field: 'keyword', 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" }
]], ]],
@@ -105,10 +118,56 @@
var id = obj.config.id; var id = obj.config.id;
var checkStatus = table.checkStatus(id); var checkStatus = table.checkStatus(id);
var othis = lay(this); var othis = lay(this);
var data = checkStatus.data;
var array = data.map(function (item) {
return item.live_room_id
})
var arrayName = data.map(function (item) {
return item.anchorName
})
switch (obj.event) { switch (obj.event) {
case 'getCheckData': case 'deleteArray':
var data = checkStatus.data; if (array.length === 0) {
layer.alert(layui.util.escape(JSON.stringify(data))); layer.msg('没有数据', { icon: 2 })
return
}
layer.confirm('确定删除 ' + arrayName + "?", { icon: 3 }, function () {
var loadIndex = showLoadingDialog();
deleteArrayRoomConfig(array)
.then(json => {
layer.close(loadIndex)
layer.msg(json.data, { icon: 1 });
table.reloadData('roomList', {}, false)
})
}, function () {
});
break;
case 'editArray':
if (array.length === 0) {
layer.msg('没有数据', { icon: 2 })
return
}
editArrayRoom(array)
break;
case 'deleteAll':
layer.confirm('确定删除所有配置?', { icon: 3 }, function () {
var loadIndex = showLoadingDialog();
deleteAllRoomConfig()
.then(json => {
layer.close(loadIndex)
if (json.status === -1) {
layer.msg(json.message, { icon: 2 });
} else {
layer.msg(json.data, { icon: 1 });
}
table.reloadData('roomList', {}, false)
})
}, function () {
});
break; break;
}; };
}); });

View File

@@ -24,7 +24,11 @@
<script type="text/html" id="toolDemo"> <script type="text/html" id="toolDemo">
<div class="layui-clear-space"> <div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="del">删除</a> <a class="layui-btn layui-btn-xs" lay-event="del">删除</a>
<a class="layui-btn layui-btn-xs" lay-event="input">导入关注</a> <!-- <a class="layui-btn layui-btn-xs" lay-event="input">导入关注</a> -->
<a class="layui-btn layui-btn-xs" lay-event="more">
导入
<i class="layui-icon layui-icon-down"></i>
</a>
</div> </div>
</script> </script>
<script> <script>
@@ -143,8 +147,28 @@
var checkStatus = table.checkStatus(id); var checkStatus = table.checkStatus(id);
var othis = lay(this); var othis = lay(this);
switch (obj.event) { switch (obj.event) {
case 'input': case 'more':
onClickInput(data.uid) dropdown.render({
elem: this, // 触发事件的 DOM 对象
show: true, // 外部事件触发即显示
data: [{
title: '关注列表',
id: 'follow'
}, {
title: '正在开播',
id: 'live'
}],
click: function (menudata) {
if (menudata.id === 'follow') {
onClickInput(data.uid)
} else if (menudata.id === 'live') {
onClickFollow(data.uid)
}
},
align: 'right', // 右对齐弹出
style: 'box-shadow: 1px 1px 10px rgb(0 0 0 / 12%);' // 设置额外样式
})
break; break;
}; };
}); });
@@ -172,11 +196,34 @@
window.location.href = url.toString(); window.location.href = url.toString();
} }
}); });
} }
function onClickFollow(uid) {
layer.open({
type: 2, // page 层类型
area: ['650px', '400px'],
title: '选择主播',
shade: 0.6, // 遮罩透明度
shadeClose: false, // 点击遮罩区域,关闭弹层
maxmin: true, // 允许全屏最小化
anim: 1, // 0-6 的动画形式,-1 不开启
// btn: ['确定', '取消'],
content: '/html/ui/userFollowLive.html?uid=' + uid,
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();
}
});
}
}); });
</script> </script>
<style> <style>

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,33 +23,44 @@
</div> </div>
</div> </div>
<div class="layui-col-xs7"> <div class="layui-col-xs7">
<div class="grid-demo" id="container" style="margin-top: 16px; width: 100%;"> <div class="layui-row">
<video id="videoElement" controls style="width: 100%;"></video> <div class="grid-demo" id="container" style="margin-top: 16px; width: 100%;">
<video id="videoElement" controls style="width: 100%;"></video>
</div>
</div>
<div class="layui-row">
<div class="layui-form layui-col-xs1">
<input type="checkbox" id="danmuCheckBox" title="弹幕" lay-skin="tag" lay-filter="danmuCheckBox" on
checked>
</div>
<div class="layui-col-xs9" style="text-align: center;">
&nbsp; <div id="slider" lay-options="{value: 100,input:true}"></div>
<span>弹幕透明度</span>
</div>
<div class="layui-col-xs2" style="text-align: right; align-self: center;">
弹幕装载数:<span id="danmuSize">你猜</span>
</div>
</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-col-xs2">&nbsp;</div>
<div class="layui-form layui-col-xs1">
<input type="checkbox" id="danmuCheckBox" title="弹幕" lay-skin="tag" lay-filter="danmuCheckBox" on checked>
</div>
<div class="layui-col-xs5" style="text-align: center;"> <div class="layui-col-xs2" style="margin-left: 16px; width: 22%; margin-top: 16px;">
&nbsp; <div id="slider" lay-options="{value: 100,input:true}"></div> <div class="layui-row">
<span>弹幕透明度</span> <div class="layui-col-md12">
<canvas id="giftChart" style="width: 100%; max-height: 100%;"></canvas>
</div>
</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> </div>
<div class="layui-row">
<div class="layui-col-xs2">&nbsp;</div>
<div class="layui-col-xs7"> <!-- <div style="margin-bottom: 10vh;"></div> -->
<canvas id="giftChart" height="200"></canvas>
</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">
@@ -232,45 +239,71 @@
initChart(roomId, options.id) initChart(roomId, options.id)
}); });
var chartView = null;
function initChart(roomId, videoId) { function initChart(roomId, videoId) {
getVideoGiftInfo(roomId, videoId) getVideoGiftInfo(roomId, videoId)
.then(data => { .then(json => {
if (data.status != 100) { if (json.status != 100) {
return return
} }
var lables = []; var lables = [];
var values = []; var values = [];
data.data.guardInfo.forEach(item => { json.data.guardInfo.forEach(item => {
lables.push(item.gift_name+"\n"+item.total_price/100+"¥") lables.push(item.gift_name + "\n" + item.total_price + "¥")
values.push(item.total_num) values.push(item.total_num)
}); });
data.data.giftInfo.forEach(item => { json.data.giftInfo.forEach(item => {
lables.push(item.gift_name+"\n"+item.total_price/100+"¥") lables.push(item.gift_name + "\n" + item.total_price + "¥")
values.push(item.total_gift_num) values.push(item.total_gift_num)
}); });
setChart(lables, values, json)
new Chart($('#giftChart').get(0), { initSC(json.data.superChat)
type: 'pie',
data: {
labels: lables,
datasets: [{
label: '礼物(总额:'+data.data.price/100+"¥)",
data: values,
borderWidth: 1
}
]
},
options: {
scales: {
y: {
beginAtZero: true
}
}
}
});
}) })
} }
function setChart(lables, values, data) {
if (chartView !== null) {
chartView.destroy()
}
chartView = new Chart($('#giftChart').get(0), {
type: 'pie',
data: {
labels: lables,
datasets: [{
label: '礼物(总额:' + data.data.price + "¥)",
data: values,
borderWidth: 1
}
]
},
options: {
scales: {
y: {
beginAtZero: true
}
},
onClick: (e) => {
var giftName = e.chart.tooltip.title[0]
console.log(e.chart.tooltip.title)
layer.open({
type: 2, // page 层类型
area: ['650px', '400px'],
title: '礼物详情',
shade: 0.6, // 遮罩透明度
shadeClose: false, // 点击遮罩区域,关闭弹层
maxmin: true, // 允许全屏最小化
anim: 1, // 0-6 的动画形式,-1 不开启
// btn: ['确定', '取消'],
content: '/html/ui/videoGiftItem.html?roomId=' + roomId + "&videoId=" + videoId + "&giftName=" + giftName,
yes: function (index, layero) {
}
});
}
}
});
}
function initMenu(data) { function initMenu(data) {
var view = $("#menuView").get(0); var view = $("#menuView").get(0);
laytpl($('#menulist').get(0).innerHTML).render(data, function (html) { laytpl($('#menulist').get(0).innerHTML).render(data, function (html) {
@@ -313,7 +346,8 @@
} }
function playVideo(url) { function playVideo(url) {
var video = document.getElementById('videoElement'); var video = document.getElementById('videoElement');
var videoSrc = url; var videoSrc = window.location.origin + url;
console.log('url = ' + videoSrc)
var hls = new Hls(); var hls = new Hls();
hls.loadSource(videoSrc); hls.loadSource(videoSrc);
hls.attachMedia(video); hls.attachMedia(video);
@@ -325,12 +359,22 @@
} }
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) {
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.danmu.length === 3000) {
loadDanmu(page + 1)
}
// barrageRenderer.setBarrages(data.data.danmu);
}) })
} }
function initSC(scList) { function initSC(scList) {
flow.load({ flow.load({
@@ -339,10 +383,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

@@ -1,5 +1,10 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head> <head>
<meta charset="utf-8">
<link rel="stylesheet" href="/layui/css/layui.css"> <link rel="stylesheet" href="/layui/css/layui.css">
<link rel="stylesheet" href="/css/inputTag.css">
<style> <style>
.layui-form-label { .layui-form-label {
width: 120px !important; width: 120px !important;
@@ -29,25 +34,43 @@
<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="timeTips(3,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">
<input type="checkbox" checked name="week_1" title="周一" lay-skin="tag"> <input type="checkbox" name="week_1" title="周一" lay-skin="tag">
<input type="checkbox" checked name="week_2" title="周二" lay-skin="tag"> <input type="checkbox" name="week_2" title="周二" lay-skin="tag">
<input type="checkbox" checked name="week_3" title="周三" lay-skin="tag"> <input type="checkbox" name="week_3" title="周三" lay-skin="tag">
<input type="checkbox" checked name="week_4" title="周四" lay-skin="tag"> <input type="checkbox" name="week_4" title="周四" lay-skin="tag">
<input type="checkbox" checked name="week_5" title="周五" lay-skin="tag"> <input type="checkbox" name="week_5" title="周五" lay-skin="tag">
<input type="checkbox" checked name="week_6" title="周六" lay-skin="tag"> <input type="checkbox" name="week_6" title="周六" lay-skin="tag">
<input type="checkbox" checked name="week_7" title="周日" lay-skin="tag"> <input type="checkbox" name="week_7" title="周日" lay-skin="tag">
</div> </div>
</div> </div>
<div class="layui-form-item">
<label class="layui-form-label">关键词检测</label>
<div class="layui-input-inline fairy-tag-container" style="width: 60%;">
<input type="text" id="keywordList" name="keywordList" autocomplete="off"
class="layui-input fairy-tag-input ">
</div>
<i class="layui-icon layui-icon-help" onclick="timeTips(4,this)"></i>
</div>
<div class="layui-form-item"> <div class="layui-form-item">
<label class="layui-form-label">弹幕录制时间</label> <label class="layui-form-label">弹幕录制时间</label>
<div class="layui-input-inline"> <div class="layui-input-inline">
<input type="text" name="recordDanmuDate" id="recordDanmuDate" value="00:00:00 - 23:59:59" <input type="text" name="recordDanmuDate" id="recordDanmuDate" value="00:00:00 - 23:59:59"
lay-verify="required" autocomplete="off" class="layui-input"> lay-verify="required" autocomplete="off" class="layui-input">
</div> </div>
<div class="layui-form-mid layui-text-em" onclick="timeTips(false,this)"><i <div class="layui-form-mid layui-text-em" onclick="timeTips(1,this)"><i
class="layui-icon layui-icon-help"></i> </div> class="layui-icon layui-icon-help"></i> </div>
</div> </div>
<div class="layui-form-item"> <div class="layui-form-item">
@@ -56,7 +79,7 @@
<input type="text" name="recordLiveDate" id="recordLiveDate" value="00:00:00 - 23:59:59" <input type="text" name="recordLiveDate" id="recordLiveDate" value="00:00:00 - 23:59:59"
lay-verify="required" autocomplete="off" class="layui-input"> lay-verify="required" autocomplete="off" class="layui-input">
</div> </div>
<div class="layui-form-mid layui-text-em" onclick="timeTips(true,this)"><i <div class="layui-form-mid layui-text-em" onclick="timeTips(2,this)"><i
class="layui-icon layui-icon-help"></i> </div> class="layui-icon layui-icon-help"></i> </div>
</div> </div>
<div class="layui-inline"> <div class="layui-inline">
@@ -88,23 +111,38 @@
<script src="/js/jquery-3.2.1.js"></script> <script src="/js/jquery-3.2.1.js"></script>
<script src="/js/httpUtils.js"></script> <script src="/js/httpUtils.js"></script>
<script src="/js/CommonConfig.js"></script> <script src="/js/CommonConfig.js"></script>
<script src="/js/inputTag.js"></script>
<script> <script>
function timeTips(isLive,that) { function timeTips(type, that) {
if(isLive){ var tips = ''
layer.tips('是从开始到结束时间范围内,主播开播会启动录制,超过结束范围不会中断正在录制的任务', that); switch (type) {
}else{ case 1: tips = '仅在时间范围内录制,如遇到正在直播,则延迟到下播时停止录制<p style="color:red;">注:如果启用同步录制功能,那本设置将无效</p>'; break;
layer.tips('仅在当前时间范围内录制,如遇到正在直播,则延迟到下播时停止录制', that); case 2: tips = '仅在时间范围内录制,主播开播会启动录制,正在直播时超过结束范围则延迟到下播时停止录制'; break;
case 3: tips = '启用后,录制视频时会同步录制弹幕,下播后会同步停止录制.同时上面录制弹幕按钮将失效<p style="color:red;">启用该选项必须启用录制视频功能才可有效</p>'; break;
case 4: tips = '在监测时间范围内(周X、时段),开播标题包含关键词才会开始录制<p style="color:red;">仅针对录制视频功能,录制弹幕不受关键词影响</p>'; break;
} }
layer.alert(tips)
} }
var roomId = getParam("roomId"); var roomId = getParam("roomId");
var editArray = getParam("array")
layui.use(['form', 'laytpl', 'laydate'], function () { layui.use(['form', 'laytpl', 'laydate'], function () {
var form = layui.form; var form = layui.form;
var layer = layui.layer; var layer = layui.layer;
var laytpl = layui.laytpl; var laytpl = layui.laytpl;
var laydate = layui.laydate; var laydate = layui.laydate;
var inputTag = layui.inputTag;
var windowsIndex; var windowsIndex;
var keywordList=[];
inputTag = inputTag.render({
elem: '#keywordList',
data: [],
onChange: function (data, value, type) {
keywordList = data;
}
});
form.render(); form.render();
// 提交事件 // 提交事件
form.on('submit(submit-form)', function (data) { form.on('submit(submit-form)', function (data) {
@@ -121,17 +159,38 @@
weeks.push(i.toString()); weeks.push(i.toString());
} }
} }
field.keywordList = keywordList
field.weeks = weeks; field.weeks = weeks;
console.log(field) console.log(field)
addRoomConfig(field) if (editArray === null) {
.then(json => { var loadIndex = showLoadingDialog();
layer.msg(json.message, function () { addRoomConfig(field)
if (json.status == 100) { .then(json => {
close() layer.close(loadIndex)
} layer.msg(json.message, function () {
}) if (json.status == 100) {
close()
}
})
})
} else {
var array = []
editArray.split(',').forEach(element => {
array.push(element)
});
var loadIndex = showLoadingDialog();
setArrayRoomConfig(array, field)
.then(json => {
layer.close(loadIndex)
layer.msg(json.message, function () {
if (json.status == 100) {
close()
}
})
})
}
})
return false; // 阻止默认 form 跳转 return false; // 阻止默认 form 跳转
}); });
@@ -149,12 +208,23 @@
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) {
$('#btn_reset').get(0).disabled = true; $('#btn_reset').get(0).disabled = true;
editRoom() editRoom()
} }
if (editArray !== null) {
editArrayRoom()
}
} }
function getUserInfo() { function getUserInfo() {
getBiliAllUser() getBiliAllUser()
@@ -178,17 +248,42 @@
'recordDanmu': json.recordDanmu, 'recordDanmu': json.recordDanmu,
'recordDanmuDate': json.recordDanmuDate, 'recordDanmuDate': json.recordDanmuDate,
'recordLiveDate': json.recordLiveDate, 'recordLiveDate': json.recordLiveDate,
'syncDanmuForLive': json.syncDanmuForLive,
'week_1':false,
'week_2':false,
'week_3':false,
'week_4':false,
'week_5':false,
'week_6':false,
'week_7':false,
}; };
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); if(json.keyword===undefined||json.keyword===null){
json.keyword=[]
}
keywordList = json.keyword;
inputTag.setData(json.keyword)
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;
} }
}) })
} }
function editArrayRoom() {
$('#btn_reset').get(0).disabled = true;
$('#url').get(0).disabled = true;
var result = {
'url': editArray
};
form.val('form-filter', result);
}
init(); init();
}); });
</script> </script>

View File

@@ -46,7 +46,7 @@
elem: '#followList', elem: '#followList',
url: '/live/config/follow?userId=' + userId, url: '/live/config/follow?userId=' + userId,
toolbar: '#toolbarDemo', toolbar: '#toolbarDemo',
height: '350px', height: 'full',
totalRow: true, // 开启合计行 totalRow: true, // 开启合计行
page: true, page: true,
response: { response: {

View File

@@ -0,0 +1,132 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/layui/css/layui.css">
</head>
<table class="layui-hide" id="followList" lay-filter="followTable"></table>
<body>
</body>
<script src="/layui/layui.js"></script>
<script src="/js/jquery-3.2.1.js"></script>
<script src="/js/CommonConfig.js"></script>
<script src="/js/httpUtils.js"></script>
<script type="text/html" id="toolbarDemo">
<div class="layui-btn-container">
<button class="layui-btn layui-btn-sm" lay-event="select">配置选择项</button>
</div>
</script>
<script type="text/html" id="toolDemo">
<div class="layui-clear-space">
<a class="layui-btn layui-btn-xs" lay-event="add">添加</a>
</div>
</script>
<script>
var userId = getParam("uid");
layui.use(function () {
var laytpl = layui.laytpl;
var table = layui.table;
table.render({
elem: '#followList',
url: '/live/config/followLive?userId=' + userId,
toolbar: '#toolbarDemo',
height: 'full',
totalRow: false, // 开启合计行
page: false,
response: {
statusCode: 100 // 重新规定成功的状态码为 200table 组件默认为 0
},
parseData: function (res) {
return {
"code": res.status, //解析接口状态
"msg": res.message, //解析提示文本
"data": res.data, //解析数据列表
"count": res.count
};
},
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'uname', title: '用户名', width: 150, fixed: 'left' },
{ field: 'uid', title: 'UID', width: 150, sort: true, templet: '<div><a href="https://space.bilibili.com/{{= d.uid}}" target="_blank">{{= d.uid}}</a>' },
{ field: 'room_id', title: '房间号', width: 150, sort: true, templet: '<div><a href="https://live.bilibili.com/{{= d.room_id}}" target="_blank">{{= d.room_id}}</a>' },
{ field: 'face', title: '头像', width: 80, templet: '<div><image src="" onerror="showImage(\'{{= d.face }}\',this);" style="width: 30px;height: 30px;"></div>' },
{ field: 'title', title: '标题', width: 150, },
{ fixed: "right", title: "操作", width: 50, align: "center", toolbar: "#toolDemo" }
]],
done: function () {
onDone()
},
error: function (res, msg) {
console.log(res, msg)
}
});
function onDone() {
table.on('toolbar(followTable)', function (obj) {
if (obj.event === 'select') {
var data = table.checkStatus(obj.config.id).data;
console.log(data)
var array = []
data.forEach(item => {
array.push({
"roomId": item.room_id,
"uname": item.uname
})
});
var loadIndex = layer.msg('配置中', {
icon: 16,
shade: 0.6
});
addFollowRoomList(JSON.stringify(array))
.then(json => {
layer.close(loadIndex)
layer.msg("已成功配置" + json.count + "个房间")
})
}
})
table.on('tool(followTable)', function (obj) {
if (obj.event === 'add') {
var loadIndex = layer.msg('配置中', {
icon: 16,
shade: 0.6
});
var array = []
array.push({
"roomId": obj.data.room_id,
"uname": obj.data.uname
})
addFollowRoomList(JSON.stringify(array))
.then(data => {
layer.close(loadIndex)
layer.msg("已成功配置" + data.count + "个房间")
})
}
})
}
function openTips(message, mid) {
layer.alert(message, {
title: "提示",
btn: ['打开配置页', '前往UP主页', '取消'],
btnAlign: 'c', // 按钮居中显示
btn1: function () {
window.open("/html/body/live.html?type=createRoom", '_blank')
},
btn2: function () {
window.open("https://space.bilibili.com/" + mid, '_blank')
},
btn3: function () {
}
});
}
})
</script>

View File

@@ -0,0 +1,68 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="utf-8">
<link rel="stylesheet" href="/layui/css/layui.css">
</head>
<table class="layui-hide" id="giftList" lay-filter="followTable"></table>
<body>
</body>
<script src="/layui/layui.js"></script>
<script src="/js/jquery-3.2.1.js"></script>
<script src="/js/CommonConfig.js"></script>
<script src="/js/httpUtils.js"></script>
<script>
var roomId = getParam("roomId");
var videoId = getParam("videoId");
var giftName = getParam("giftName");
layui.use(function () {
var laytpl = layui.laytpl;
var table = layui.table;
table.render({
elem: '#giftList',
url: '/live/gift/info/item?roomId=' + roomId + "&videoId=" + videoId + "&giftName=" + giftName,
height: 'full',
totalRow: false, // 开启合计行
page: false,
response: {
statusCode: 100 // 重新规定成功的状态码为 200table 组件默认为 0
},
parseData: function (res) {
return {
"code": res.status, //解析接口状态
"msg": res.message, //解析提示文本
"data": res.data, //解析数据列表
"count": res.count
};
},
cols: [[
{ type: 'checkbox', fixed: 'left' },
{ field: 'icon', title: '礼物', width: 80, templet: '<div><image src="" onerror="showImage(\'{{= d.icon }}\',this);" style="width: 30px;height: 30px;"></div>' },
{ field: 'gift_id', title: '礼物ID', width: 80, sort: true },
{ field: 'sender_name', title: '用户名', width: 150, sort: true, templet: '<div><a href="https://space.bilibili.com/{{= d.sender_uid}}" target="_blank">{{= d.sender_name}}</a>' },
{ field: 'sender_face', title: '用户头像', width: 80, templet: '<div><image src="" onerror="showImage(\'{{= d.sender_face }}\',this);" style="width: 30px;height: 30px;"></div>' },
{ field: 'gift_num', title: '数量', width: 80, sort: true },
{ field: 'price', title: '价值(电池)', width: 80, sort: true },
{ field: 'sql_time', title: '时间', width: 180 }
]],
done: function () {
onDone()
},
error: function (res, msg) {
console.log(res, msg)
}
});
function onDone() {
}
})
</script>

View File

@@ -10,7 +10,7 @@
<body> <body>
<div id="header"></div> <div id="header"></div>
<div class="layui-row layui-col-space15"> <div class="layui-row">
<div id="follow"> <div id="follow">
</div> </div>
<div id="card"></div> <div id="card"></div>
@@ -59,21 +59,24 @@
</span> </span>
</div> </div>
<div class="layui-card-body"> <div class="layui-card-body">
<img src="#" onerror="showTmpImage('{{= item.cover}}',this)" onclick="toLive('{{= item.roomId}}')" style="width: 100%; height: 100%; object-fit: cover;"/><br> <div style="overflow: hidden;position: relative; width: 100%; height: 100%; padding-bottom: 52%;">
<img src="#" onerror="showTmpImage('{{= item.cover}}',this)" onclick="toLive('{{= item.roomId}}')" style="max-width: 100%; max-height: 100%; object-fit: cover; display: block; margin: auto; position: absolute; "/><br>
</div>
<!-- <img src="#" onerror="showImage('{{= item.cover}}',this)" onclick="toLive('{{= item.roomId}}')" style="width: 100%; height: 100%;"/><br> --> <!-- <img src="#" onerror="showImage('{{= item.cover}}',this)" onclick="toLive('{{= item.roomId}}')" style="width: 100%; height: 100%;"/><br> -->
{{= item.title}}<br> <span style="white-space: nowrap; overflow: hidden; text-overflow: ellipsis; ">{{= item.title}}</span> <br>
<p>开播时长:{{= item.liveTime}}</p> <p>开播时长:{{= item.liveTime}}</p>
直播录制状态: 直播录制状态:
{{# if(item.downloadVideo){ }} {{# if(item.downloadVideo){ }}
<span style="color: #16b777" onclick="clickVideo('{{= item.roomId}}',true)">录制中</span> <span style="color: #16b777" onclick="clickVideo('{{= item.roomId}}',true,this)">{{= item.videoListen}}</span>
{{# } else{ }} {{# } else{ }}
<span style="color: #FD482C" onclick="clickVideo('{{= item.roomId}}',false)">待机中</span> <span style="color: #FD482C" onclick="clickVideo('{{= item.roomId}}',false,this)">{{= item.videoListen}}</span>
{{# }; }}<br> {{# }; }}<br>
弹幕录制状态: 弹幕录制状态:
{{# if(item.danmu){ }} {{# if(item.danmu){ }}
<span style="color: #16b777" onclick="clickDanmu('{{= item.roomId}}',true)">录制中</span> <span style="color: #16b777" onclick="clickDanmu('{{= item.roomId}}',true,this)">{{= item.danmuListen}}</span>
{{# } else{ }} {{# } else{ }}
<span style="color: #FD482C" onclick="clickDanmu('{{= item.roomId}}',false)">待机中</span> <span style="color: #FD482C" onclick="clickDanmu('{{= item.roomId}}',false,this)">{{= item.danmuListen}}</span>
{{# }; }}<br> {{# }; }}<br>
</div> </div>
</div> </div>
@@ -95,21 +98,21 @@
console.log(roomId) console.log(roomId)
window.open("https://live.bilibili.com/" + roomId, '_blank') window.open("https://live.bilibili.com/" + roomId, '_blank')
} }
function clickVideo(roomId, status) { function clickVideo(roomId, status, that) {
const title = "是否" + (status ? "停止" : "启动") + "录制视频?" const title = "是否" + (status ? "停止" : "启动") + "录制视频?<p style='color:red;'>手动干预后不再自动监听,第二天或重新配置可清除该状态</p>"
layer.confirm(title, { icon: 3 }, function () { layer.confirm(title, { icon: 3 }, function () {
if (status) { if (status) {
stopLiveVideo(roomId) stopLiveVideo(roomId)
.then(data => { .then(data => {
layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () { layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () {
location.reload(); //location.reload();
}); });
}) })
} else { } else {
startLiveVideo(roomId) startLiveVideo(roomId)
.then(data => { .then(data => {
layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () { layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () {
location.reload(); //location.reload();
}); });
}) })
} }
@@ -119,13 +122,13 @@
}); });
} }
function clickDanmu(roomId, status) { function clickDanmu(roomId, status) {
const title = "是否" + (status ? "停止" : "启动") + "录制弹幕?" const title = "是否" + (status ? "停止" : "启动") + "录制弹幕?<p style='color:red;'>手动干预后不再自动监听,第二天或重新配置可清除该状态</p>"
layer.confirm(title, { icon: 3 }, function () { layer.confirm(title, { icon: 3 }, function () {
if (status) { if (status) {
stopLiveDanmu(roomId) stopLiveDanmu(roomId)
.then(data => { .then(data => {
layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () { layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () {
location.reload(); // location.reload();
}); });
}) })
@@ -133,12 +136,12 @@
startLiveDanmu(roomId) startLiveDanmu(roomId)
.then(data => { .then(data => {
layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () { layer.msg(data.message, { icon: (data.status == 100 ? 1 : 0) }, function () {
location.reload(); // location.reload();
}); });
}) })
} }
}, function () { }, function () {
layer.msg('点击取消的回调');
}); });
} }
function confirmFollow(userId) { function confirmFollow(userId) {
@@ -195,7 +198,7 @@
// if(document.getElementById(item.data)!==null){ // if(document.getElementById(item.data)!==null){
// document.getElementById(item.data).innerHTML=item.message; // document.getElementById(item.data).innerHTML=item.message;
// } // }
// }) // })
initFollowStatus(true) initFollowStatus(true)

View File

@@ -1,71 +1,86 @@
function get(url){ function get(url) {
return fetch(url) return fetch(url)
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`); throw new Error(`HTTP error! Status: ${response.status}`);
} }
return response.json();
})
.then(data => {
return data;
})
.catch(error => {
return error;
});
}
function post(url,formData,isJSON) {
return fetch(url, {
method: "POST",
body: formData
})
.then(response => {
if (!response.ok) {
throw new Error("Network response was not ok");
}
if(isJSON){
return response.json(); return response.json();
})
.then(data => {
return data;
})
.catch(error => {
return error;
});
}
function post(url, formData, isJSON) {
return sendPost(url, true, formData, isJSON)
}
function sendPost(url, isFormData, formData, isJSON) {
var obj = {};
if (isFormData) {
obj = {
method: "POST",
body: formData
} }
return response.blob(); } else {
}) obj = {
.then(blob => { method: "POST",
if(isJSON){ headers: {
return blob; 'Content-Type': 'application/json'
},
body: JSON.stringify(formData)
} }
const imageUrl = URL.createObjectURL(blob); }
return imageUrl; return fetch(url, obj)
}) .then(response => {
.catch(error => { if (!response.ok) {
console.error("There was a problem with the fetch operation:", error); throw new Error("Network response was not ok");
throw error; // 重新抛出错误以便外部捕获 }
}); if (isJSON) {
return response.json();
}
return response.blob();
})
.then(blob => {
if (isJSON) {
return blob;
}
const imageUrl = URL.createObjectURL(blob);
return imageUrl;
})
.catch(error => {
console.error("There was a problem with the fetch operation:", error);
throw error; // 重新抛出错误以便外部捕获
});
} }
function post2blob(url,formData) { function post2blob(url, formData) {
return fetch(url, { return fetch(url, {
method: "POST", method: "POST",
body: formData body: formData
}) })
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
throw new Error("Network response was not ok"); throw new Error("Network response was not ok");
} }
return response.text(); return response.text();
}) })
.then(blob => { .then(blob => {
return blob; return blob;
}) })
.catch(error => { .catch(error => {
console.error("There was a problem with the fetch operation:", error); console.error("There was a problem with the fetch operation:", error);
throw error; // 重新抛出错误以便外部捕获 throw error; // 重新抛出错误以便外部捕获
}); });
} }
function buildFormData(json){ function buildFormData(json) {
const formData = new FormData(); const formData = new FormData();
Object.keys(json).forEach(key => { Object.keys(json).forEach(key => {
const value = json[key]; const value = json[key];
if (Array.isArray(value)) { if (Array.isArray(value)) {
value.forEach((item, index) => { value.forEach((item, index) => {
formData.append(`${key}[${index}]`, item); formData.append(`${key}[${index}]`, item);
@@ -81,101 +96,119 @@ function buildFormData(json){
//----------------直播配置相关接口 //----------------直播配置相关接口
function getLiveVideoList() { function getLiveVideoList() {
return get("/live/video/list"); return get("/live/video/list");
} }
function getHttpImage(url) { function getHttpImage(url) {
const encode = encodeURI(url); const encode = encodeURI(url);
const formData = new FormData(); const formData = new FormData();
formData.append("url", encode); formData.append("url", encode);
return post("/file/img",formData,false) return post("/file/img", formData, false)
.then(blob=>{ .then(blob => {
return blob; return blob;
}) })
} }
function getHttpTmpImage(url) { function getHttpTmpImage(url) {
const encode = encodeURI(url); const encode = encodeURI(url);
const formData = new FormData(); const formData = new FormData();
formData.append("url", encode); formData.append("url", encode);
return post2blob("/file/imgTmp",formData) return post2blob("/file/imgTmp", formData)
.then(blob=>{ .then(blob => {
return blob; return blob;
}) })
} }
function addRoomConfig(json){ function addRoomConfig(config) {
const formData=buildFormData(json) const formData = buildFormData(config)
return post("/live/config/set",formData,true) return post("/live/config/set", formData, true)
} }
function getRoomConfig(roomId){ function setArrayRoomConfig(rooms, config) {
return get("/live/config/get?roomId="+roomId) var json = {
"config": config,
"array": rooms
}
return sendPost("/live/config/set/array", false, json, true)
} }
function deleteRoomConfig(roomId){ function deleteArrayRoomConfig(rooms) {
return get("/live/config/delete?roomId="+roomId) return sendPost("/live/config/delete/array", false, rooms, true)
} }
function checkFollowStatus(userId){ function deleteAllRoomConfig() {
return get("/live/config/follow/check?userId="+userId) return get("/live/config/delete/all")
} }
function confirmFollowStatus(userId){ function getRoomConfig(roomId) {
return get("/live/config/follow/confirm?userId="+userId) return get("/live/config/get?roomId=" + roomId)
} }
function addAllFollow(userId){ function deleteRoomConfig(roomId) {
return get("/live/config/follow/all?userId="+userId) return get("/live/config/delete?roomId=" + roomId)
} }
function addFollow(uid,anchorId){ function checkFollowStatus(userId) {
return get("/live/config/follow/add?anchorId="+anchorId+"&uid="+uid) return get("/live/config/follow/check?userId=" + userId)
} }
function addFollowList(uid,array){ function confirmFollowStatus(userId) {
return get("/live/config/follow/confirm?userId=" + userId)
}
function addAllFollow(userId) {
return get("/live/config/follow/all?userId=" + userId)
}
function addFollow(uid, anchorId) {
return get("/live/config/follow/add?anchorId=" + anchorId + "&uid=" + uid)
}
function addFollowList(uid, array) {
const formData = new FormData(); const formData = new FormData();
formData.append("array", array); formData.append("array", array);
return post("/live/config/follow/addList?uid="+uid,formData,true) return post("/live/config/follow/addList?uid=" + uid, formData, true)
}
function addFollowRoomList(array) {
const formData = new FormData();
formData.append("array", array);
return post("/live/config/follow/roomId/addList", formData, true)
} }
//----------------直播配置相关接口end //----------------直播配置相关接口end
//----------------首页相关接口 //----------------首页相关接口
function getAllLive(page,limit){ function getAllLive(page, limit) {
return get("/live/list?page="+page+"&limit="+limit) return get("/live/list?page=" + page + "&limit=" + limit)
} }
function getAllConfig(page,limit){ function getAllConfig(page, limit) {
return get("/live/config/all?page="+page+"&limit="+limit) return get("/live/config/all?page=" + page + "&limit=" + limit)
} }
//----------------首页相关接口end //----------------首页相关接口end
//----------------直播视频相关接口 //----------------直播视频相关接口
function startLiveVideo(roomId){ function startLiveVideo(roomId) {
return get("/live/video/start?roomId="+roomId) return get("/live/video/start?roomId=" + roomId)
} }
function stopLiveVideo(roomId){ function stopLiveVideo(roomId) {
return get("/live/video/stop?roomId="+roomId) return get("/live/video/stop?roomId=" + roomId)
} }
function getVideo(roomId,page,limit){ function getVideo(roomId, page, limit) {
return get("/file/list?roomId="+roomId+"&page="+page+"&limit="+limit) return get("/file/list?roomId=" + roomId + "&page=" + page + "&limit=" + limit)
} }
function getPlayerVideo(roomId,videoId){ function getPlayerVideo(roomId, videoId) {
return get('/video/play?roomId='+roomId+"&videoId="+videoId) return get('/video/play?roomId=' + roomId + "&videoId=" + videoId)
} }
//----------------直播视频相关接口end //----------------直播视频相关接口end
//----------------弹幕相关接口 //----------------弹幕相关接口
function startLiveDanmu(roomId){ function startLiveDanmu(roomId) {
return get("/live/danmu/start?roomId="+roomId) return get("/live/danmu/start?roomId=" + 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
//----------------用户相关接口 //----------------用户相关接口
function getBiliAllUser(){ function getBiliAllUser() {
return get("/user/list") return get("/user/list")
} }
function login(){ function login() {
return get("/user/login") return get("/user/login")
} }
function refreshCookie(userId){ function refreshCookie(userId) {
return get("/user/refreshCookie?uid="+userId) return get("/user/refreshCookie?uid=" + userId)
} }
//----------------用户相关接口end //----------------用户相关接口end
//----------------礼物相关接口 //----------------礼物相关接口
function getVideoGiftInfo(roomId,videoId){ function getVideoGiftInfo(roomId, videoId) {
return get("/live/gift/info?roomId="+roomId+"&videoId="+videoId) return get("/live/gift/info?roomId=" + roomId + "&videoId=" + videoId)
} }
//----------------礼物相关接口end //----------------礼物相关接口end

170
Web/js/inputTag.js Normal file
View File

@@ -0,0 +1,170 @@
/*
* Name: inputTag
* Author: cshaptx4869
* Project: https://github.com/cshaptx4869/inputTag
*/
(function (define) {
define(['jquery'], function ($) {
"use strict";
class InputTag {
options = {
elem: '.fairy-tag-input',
theme: ['fairy-bg-red', 'fairy-bg-orange', 'fairy-bg-green', 'fairy-bg-cyan', 'fairy-bg-blue', 'fairy-bg-black'],
data: [],
removeKeyNum: 8,
createKeyNum: 13,
permanentData: [],
sortable: false
};
get elem() {
return $(this.options.elem);
}
get copyData() {
return [...this.options.data];
}
constructor(options) {
this.render(options);
}
render(options) {
this.init(options);
this.listen();
}
setData(data){
this.options.data=data;
this.init(this.options)
}
init(options) {
var spans = '', that = this;
this.options = $.extend(this.options, options);
!this.elem.attr('placeholder') && this.elem.attr('placeholder', '添加标签');
$.each(this.options.data, function (index, item) {
spans += that.spanHtml(item);
});
this.elem.before(spans);
}
listen() {
var that = this;
this.elem.parent().on('click', 'a', function () {
that.removeItem($(this).parent('span'));
});
this.elem.parent().on('click', function () {
that.elem.focus();
});
this.elem.keydown(function (event) {
var keyNum = (event.keyCode ? event.keyCode : event.which);
if (keyNum === that.options.removeKeyNum) {
if (!that.elem.val().trim()) {
var closeItems = that.elem.parent().find('a');
if (closeItems.length) {
that.removeItem($(closeItems[closeItems.length - 1]).parent('span'));
event.preventDefault();
}
}
} else if (keyNum === that.options.createKeyNum) {
that.createItem();
event.preventDefault();
}
});
this.elem.blur(function () {
that.createItem();
});
if (this.options.sortable) {
Sortable.create(this.elem.parent()[0], {
handle: 'span',
onSort: function (event) {
that.options.data = that.elem.parent()
.find('span.fairy-tag>span')
.map(function (index, item) {
return $(item).text();
}).toArray();
that.onChange($(event.item).children('span').text(), 'sort');
}
});
}
}
createItem() {
var value = this.elem.val().trim();
if (this.options.beforeCreate && typeof this.options.beforeCreate === 'function') {
var modifiedValue = this.options.beforeCreate(this.copyData, value);
if (typeof modifiedValue == 'string' && modifiedValue) {
value = modifiedValue;
} else {
value = '';
}
}
if (value) {
if (!this.options.data.includes(value)) {
this.options.data.push(value);
this.elem.before(this.spanHtml(value));
this.onChange(value, 'create');
}
}
this.elem.val('');
}
removeItem(target) {
var that = this;
var closeSpan = target.remove(),
closeSpanText = $(closeSpan).children('span').text();
var value = that.options.data.splice($.inArray(closeSpanText, that.options.data), 1);
value.length === 1 && that.onChange(value[0], 'remove');
}
randomColor() {
return this.options.theme[Math.floor(Math.random() * this.options.theme.length)];
}
spanHtml(value) {
return '<span class="fairy-tag fairy-anim-fadein ' + this.randomColor() + '">' +
'<span>' + value + '</span>' +
(this.options.permanentData.includes(value) ? '' : '<a href="#" title="删除标签">&times;</a>') +
'</span>';
}
onChange(value, type) {
this.options.onChange && typeof this.options.onChange === 'function' && this.options.onChange(this.copyData, value, type);
}
getData() {
return this.copyData;
}
clearData() {
this.options.data = [];
this.elem.prevAll('span.fairy-tag').remove();
}
}
return {
render(options) {
return new InputTag(options);
}
}
});
}(typeof define === 'function' && define.amd ? define : function (deps, factory) {
var MOD_NAME = 'inputTag';
if (typeof module !== 'undefined' && module.exports) { //Node
module.exports = factory(require('jquery'));
} else if (window.layui && layui.define) {
layui.define('jquery', function (exports) { //layui加载
exports(MOD_NAME, factory(layui.jquery));
});
} else {
window[MOD_NAME] = factory(window.jQuery);
}
}));

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

15
pom.xml
View File

@@ -144,6 +144,21 @@
<artifactId>selenium-java</artifactId> <artifactId>selenium-java</artifactId>
<version>4.26.0</version> <version>4.26.0</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
<exclusions>
<exclusion>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-logging</artifactId>
</exclusion>
</exclusions>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-log4j2</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@@ -1,29 +1,32 @@
package com.yutou; package com.yutou;
import com.yutou.common.okhttp.HttpLoggingInterceptor; import com.yutou.common.okhttp.HttpLoggingInterceptor;
import com.yutou.common.utils.ConfigTools;
import com.yutou.common.utils.Log;
import org.springframework.boot.SpringApplication; import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication; import org.springframework.boot.autoconfigure.SpringBootApplication;
@SpringBootApplication() @SpringBootApplication()
public class BilibiliApplication { public class BilibiliApplication {
public static String version = "0.8.4"; public static String version = "0.10";
public static void main(String[] args) { public static void main(String[] args) {
HttpLoggingInterceptor.setLog(false); Log.i("启动版本", version);
HttpLoggingInterceptor.setLog(ConfigTools.load(ConfigTools.CONFIG, "HttpLog", Boolean.class, false));
SpringApplication.run(BilibiliApplication.class, args); SpringApplication.run(BilibiliApplication.class, args);
} }
//TODO 优化 1 手动中断录制后,没有时间戳问题需要解决,看是提示转码还是有什么其他解决方案 | 改成了通过q来退出 //TODO 优化 1 手动中断录制后,没有时间戳问题需要解决,看是提示转码还是有什么其他解决方案 | 改成了通过q来退出
//TODO 优化 2 创建nfo文件看看要不要改成录制后生成,或者触发打包指令再生成 //TODO 优化 2 创建nfo文件看看要不要改成录制后生成,或者触发打包指令再生成|改了
//TODO 优化 3 录制完成前应该也允许查看礼物信息和SC以及弹幕,未停止录制改成从开始时间到当前时间的弹幕和礼物信息,已停止录制则询问是否转码 //TODO 优化 3 录制完成前应该也允许查看礼物信息和SC以及弹幕,未停止录制改成从开始时间到当前时间的弹幕和礼物信息,已停止录制则询问是否转码|改了
//TODO 优化 4 视频页面礼物的图标,有数据计算错误以及单项猛增的话导致其他项目无法查看的问题(可参考泛式死亡笔记录播) | 换成饼状图 //TODO 优化 4 视频页面礼物的图标,有数据计算错误以及单项猛增的话导致其他项目无法查看的问题(可参考泛式死亡笔记录播) | 换成饼状图 | 改了
//TODO 修复 1 开播时有概率连续触发创建nfo和记录视频到数据宽度问题. //TODO 修复 1 开播时有概率连续触发创建nfo和记录视频到数据宽度问题. | 改了
//TODO 测试 1 需要测试网络中断下,弹幕重连机制 //TODO 测试 1 需要测试网络中断下,弹幕重连机制|好像没问题
//TODO 测试 2 在导出jar包后再测试完整的录制功能 //TODO 测试 2 在导出jar包后再测试完整的录制功能|没问题
//TODO 测试 3 需要确认项目的内存占用情况 //TODO 测试 3 需要确认项目的内存占用情况|好像还行
//TODO 开发 1 完成用户列表的所有功能,包括移出用户和筛选ck和登陆扫码二维码的优化 //TODO 开发 1 完成用户列表的所有功能,包括移出用户和筛选ck和登陆扫码二维码的优化|改了
//TODO 开发 2 数据中心 //TODO 开发 2 数据中心
//TODO 开发 3 视频页面打包导出视频和弹幕 //TODO 开发 3 视频页面打包导出视频和弹幕
//TODO 开发 4 弹幕随开播时间录制 //TODO 开发 4 弹幕随开播时间录制|有了
//TODO 开发 5 追加关键词检测 //TODO 开发 5 追加关键词检测|加了
} }

View File

@@ -66,5 +66,9 @@ public interface LiveApi {
@POST("/room/v1/Room/get_status_info_by_uids") @POST("/room/v1/Room/get_status_info_by_uids")
Call<HttpBody<Map<String,LiveAnchorInfo>>> getLiveRoomStatus(@Body Call<HttpBody<Map<String,LiveAnchorInfo>>> getLiveRoomStatus(@Body
JSONObject uids); JSONObject uids);
@GET("/xlive/web-ucenter/v1/xfetter/GetWebList")
Call<HttpBody<FollowLive>> getUserFollowLive(
@Query("hit_ab")boolean hit_ab,
@Query("_")long time
);
} }

View File

@@ -0,0 +1,107 @@
package com.yutou.biliapi.bean.live;
import com.alibaba.fastjson2.annotation.JSONField;
import com.yutou.common.okhttp.BaseBean;
import lombok.Data;
import lombok.EqualsAndHashCode;
import java.util.List;
@EqualsAndHashCode(callSuper = true)
@Data
public class FollowLive extends BaseBean {
@JSONField(name = "rooms")
List<Room> rooms;
@JSONField(name = "list")
List<Room> list;
@lombok.Data
public static class Room {
@JSONField(name = "title")
private String title;
@JSONField(name = "room_id")
private String roomId;
@JSONField(name = "uid")
private String uid;
@JSONField(name = "online")
private int online;
@JSONField(name = "live_time")
private String liveTime;
@JSONField(name = "live_status")
private int liveStatus;
@JSONField(name = "short_id")
private int shortId;
@JSONField(name = "area")
private int area;
@JSONField(name = "area_name")
private String areaName;
@JSONField(name = "area_v2_id")
private int areaV2Id;
@JSONField(name = "area_v2_name")
private String areaV2Name;
@JSONField(name = "area_v2_parent_name")
private String areaV2ParentName;
@JSONField(name = "area_v2_parent_id")
private int areaV2ParentId;
@JSONField(name = "uname")
private String uname;
@JSONField(name = "face")
private String face;
@JSONField(name = "tag_name")
private String tagName;
@JSONField(name = "tags")
private String tags;
@JSONField(name = "cover_from_user")
private String coverFromUser;
@JSONField(name = "keyframe")
private String keyframe;
@JSONField(name = "lock_till")
private String lockTill;
@JSONField(name = "hidden_till")
private String hiddenTill;
@JSONField(name = "broadcast_type")
private int broadcastType;
@JSONField(name = "is_encrypt")
private boolean isEncrypt;
@JSONField(name = "link")
private String link;
@JSONField(name = "nickname")
private String nickname;
@JSONField(name = "roomname")
private String roomName;
@JSONField(name = "roomid")
private String roomId2;
@JSONField(name = "liveTime")
private long liveTimeLong;
}
}

View File

@@ -14,13 +14,13 @@ public class LiveRoomConfig {
String roomId; String roomId;
String anchorName; String anchorName;
boolean isLogin; boolean isLogin;
String rootPath="live"; String rootPath = "live";
LiveDanmuInfo liveInfo; LiveDanmuInfo liveInfo;
LiveRoomInfo roomInfo; LiveRoomInfo roomInfo;
public String getLoginUid() { public String getLoginUid() {
if("null".equals(loginUid)){ if ("null".equals(loginUid)) {
loginUid=null; loginUid = null;
} }
return loginUid; return loginUid;
} }
@@ -42,9 +42,11 @@ public class LiveRoomConfig {
return Objects.hashCode(roomId); return Objects.hashCode(roomId);
} }
public static LiveRoomConfig buildConfig(String roomId){
@Deprecated
public static LiveRoomConfig buildConfigTmp(String roomId) {
BiliLiveConfigDatabase database = new BiliLiveConfigDatabase(); BiliLiveConfigDatabase database = new BiliLiveConfigDatabase();
LiveConfigDatabaseBean bean = database.getConfig(new String(roomId)); LiveConfigDatabaseBean bean = database.getConfig(roomId);
database.close(); database.close();
LiveRoomConfig config = new LiveRoomConfig(); LiveRoomConfig config = new LiveRoomConfig();
config.setLoginUid(bean.getRecordUid()); config.setLoginUid(bean.getRecordUid());

View File

@@ -34,10 +34,12 @@ 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=new ArrayList<>();
@JSONField(name = "weeks") @JSONField(name = "weeks")
private List<String> weeks=Arrays.asList("1","2","3","4","5","6","7"); private List<String> weeks=new ArrayList<>();
@JSONField(name = "recordPath") @JSONField(name = "recordPath")
private String recordPath = "live"; private String recordPath = "live";
@JSONField(name = "recordUid") @JSONField(name = "recordUid")

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

@@ -12,7 +12,7 @@ public class LiveGiftDatabaseBean extends AbsDatabasesBean {
@JSONField(name = "id") @JSONField(name = "id")
int id; int id;
@JSONField(name = "gift_id") @JSONField(name = "gift_id")
private int giftId; private long giftId;
@JSONField(name = "gift_name") @JSONField(name = "gift_name")
private String giftName; private String giftName;
@JSONField(name = "price") @JSONField(name = "price")
@@ -22,9 +22,9 @@ public class LiveGiftDatabaseBean extends AbsDatabasesBean {
@JSONField(name = "icon") @JSONField(name = "icon")
private String icon; private String icon;
@JSONField(name = "gift_num") @JSONField(name = "gift_num")
private int giftNum; private long giftNum;
@JSONField(name = "sender_uid") @JSONField(name = "sender_uid")
private long senderUid; private String senderUid;
@JSONField(name = "sender_name") @JSONField(name = "sender_name")
private String senderName; private String senderName;
@JSONField(name = "sender_face") @JSONField(name = "sender_face")

View File

@@ -14,6 +14,8 @@ public class LiveVideoDatabaseBean extends AbsDatabasesBean {
String roomInfoJson; String roomInfoJson;
@JSONField(name = "start_time") @JSONField(name = "start_time")
Date startTime; Date startTime;
@JSONField(name = "stop_time")
Date stopTime;
@JSONField(name = "path") @JSONField(name = "path")
String path; String path;

View File

@@ -182,7 +182,7 @@ public class WSSendGift extends WSData {
@JSONField(name = "base") @JSONField(name = "base")
private Base base; private Base base;
@JSONField(name = "uid") @JSONField(name = "uid")
private long uid; private String uid;
} }
@lombok.Data @lombok.Data

View File

@@ -2,27 +2,26 @@ package com.yutou.biliapi.databases;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.util.DateUtils;
import com.yutou.biliapi.bean.live.*; import com.yutou.biliapi.bean.live.*;
import com.yutou.biliapi.bean.live.database.*; import com.yutou.biliapi.bean.live.database.*;
import com.yutou.biliapi.bean.websocket.live.*; import com.yutou.biliapi.bean.websocket.live.*;
import com.yutou.bilibili.Tools.DateFormatUtils; import com.yutou.bilibili.datas.web.LiveWebDanmuBean;
import com.yutou.common.databases.AbsDatabasesBean; import com.yutou.common.databases.AbsDatabasesBean;
import com.yutou.common.databases.SQLiteManager; import com.yutou.common.databases.SQLiteManager;
import com.yutou.common.okhttp.HttpDownloadUtils; import com.yutou.common.okhttp.HttpDownloadUtils;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import lombok.Getter; import lombok.Getter;
import org.apache.poi.ss.usermodel.DataFormat;
import java.io.File; import java.io.File;
import java.sql.PreparedStatement;
import java.sql.ResultSet; import java.sql.ResultSet;
import java.sql.SQLException; import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;
public class BiliLiveDatabase extends SQLiteManager { public class BiliLiveDatabase extends SQLiteManager {
@Getter
LiveRoomConfig config; LiveRoomConfig config;
String fileName; String fileName;
File rootPath; File rootPath;
@@ -111,15 +110,13 @@ public class BiliLiveDatabase extends SQLiteManager {
} }
} }
public void addSource(WSData bean) { public synchronized void addSource(WSData bean) {
// Log.i("BiliLiveDatabase.addSource", config.getRoomId()); // Log.i("BiliLiveDatabase.addSource", config.getRoomId());
add(new LiveSourceDatabaseBean(bean)); add(new LiveSourceDatabaseBean(bean));
addData(bean); addData(bean);
} }
public JSONObject getGiftInfo(long startTimeLong, long endTimeLong) { public JSONObject getGiftInfo(long startTimeLong, long endTimeLong) {
String startTime = DateFormatUtils.getInstance().format(startTimeLong);
String endTime = DateFormatUtils.getInstance().format(endTimeLong);
String giftSql = "WITH filtered_gifts AS (" + String giftSql = "WITH filtered_gifts AS (" +
"SELECT " + "SELECT " +
"`gift_name`," + "`gift_name`," +
@@ -127,13 +124,13 @@ public class BiliLiveDatabase extends SQLiteManager {
"SUM(`gift_num`) AS `total_gift_num`," + "SUM(`gift_num`) AS `total_gift_num`," +
"CASE " + "CASE " +
"WHEN `coin_type` = 'silver' THEN 0 " + "WHEN `coin_type` = 'silver' THEN 0 " +
"ELSE SUM(`price` * `gift_num`) " + "ELSE SUM(`price` / 1000 * `gift_num`) " +
"END AS `total_price`" + "END AS `total_price`" +
"FROM " + "FROM " +
"`gift` " + "`gift` " +
"WHERE " + "WHERE " +
"`sql_time` >= '" + startTime + "' " + "`sql_time` >= '" + startTimeLong + "' " +
"AND `sql_time` <= '" + endTime + "' " + "AND `sql_time` <= '" + endTimeLong + "' " +
"GROUP BY " + "GROUP BY " +
"`gift_name`, `coin_type`" + "`gift_name`, `coin_type`" +
")" + ")" +
@@ -146,11 +143,11 @@ public class BiliLiveDatabase extends SQLiteManager {
"filtered_gifts " + "filtered_gifts " +
"GROUP BY " + "GROUP BY " +
"`gift_name`, `icon`;"; "`gift_name`, `icon`;";
String guardSql = "SELECT `gift_name`, SUM(`num`) AS `total_num`,SUM(`price`*`num`) as `total_price`" + String guardSql = "SELECT `gift_name`, SUM(`num`) AS `total_num`,SUM(`price` / 1000 *`num`) as `total_price`" +
"FROM `guardBuy` where `sql_time` >= '" + startTime + "' and `sql_time` <= '" + endTime + "'" + "FROM `guardBuy` where `sql_time` >= '" + startTimeLong + "' and `sql_time` <= '" + endTimeLong + "'" +
"GROUP BY `gift_name`;"; "GROUP BY `gift_name`;";
String superChatSql = "SELECT SUM(`price`*100) as `total_price`, count(`price`) as `total_count`" + String superChatSql = "SELECT SUM(`price`/ 1000) as `total_price`, count(`price`) as `total_count`" +
"FROM `superChat` where `sql_time` >= '" + startTime + "' and `sql_time` <= '" + endTime + "';"; "FROM `superChat` where `sql_time` >= '" + startTimeLong + "' and `sql_time` <= '" + endTimeLong + "';";
JSONObject json = new JSONObject(); JSONObject json = new JSONObject();
JSONArray giftInfo = get(giftSql); JSONArray giftInfo = get(giftSql);
JSONArray guardInfo = get(guardSql); JSONArray guardInfo = get(guardSql);
@@ -177,15 +174,14 @@ public class BiliLiveDatabase extends SQLiteManager {
} }
private void createInfo(LiveVideoDatabaseBean bean) { private void createInfo(LiveVideoDatabaseBean bean) {
String format = DateFormatUtils.getInstance().format(bean.getSql_time()); if (get(bean.getTableName(), " `sql_time` = '" + bean.getSql_time().getTime() + "'", LiveVideoDatabaseBean.class).isEmpty()) {
if (get(bean.getTableName(), " `sql_time` = '" + format + "'", LiveVideoDatabaseBean.class).isEmpty()) {
add(bean); add(bean);
} else { } else {
update(bean); update(bean);
} }
} }
public <T extends AbsDatabasesBean> List<T> getOfTime(String startTime, String endTime, Class<T> clazz) { public <T extends AbsDatabasesBean> List<T> getOfTime(Long startTime, Long endTime, Class<T> clazz) {
String tableName = null; String tableName = null;
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
String where = null; String where = null;
@@ -210,9 +206,64 @@ public class BiliLiveDatabase extends SQLiteManager {
return super.get(tableName, where, clazz); return super.get(tableName, where, clazz);
} }
public List<LiveWebDanmuBean.Danmu> getDanmu(Long startTime, Long endTime, int page, int pageSize) {
List<LiveWebDanmuBean.Danmu> list = new ArrayList<>();
String query = "SELECT danmu AS text, fontSize, color, (sql_time - ?) AS `time`, "
+ "CASE WHEN model < 4 THEN 'scroll' "
+ " WHEN model = 4 THEN 'bottom' "
+ " WHEN model = 5 THEN 'top' "
+ "END AS model "
+ "FROM danmu "
+ "WHERE sql_time BETWEEN ? AND ? "
+ "ORDER BY sql_time "
+ "LIMIT ?, ?";
try (PreparedStatement pstmt = conn.prepareStatement(query)) {
// 设置参数
pstmt.setLong(1, startTime);
pstmt.setLong(2, startTime);
pstmt.setLong(3, endTime);
pstmt.setInt(4, (page - 1) * pageSize); // 计算偏移量
pstmt.setInt(5, pageSize);
pstmt.closeOnCompletion();
ResultSet rs = pstmt.executeQuery();
while (rs.next()) {
String text = rs.getString("text");
int fontSize = rs.getInt("fontSize");
String color = rs.getString("color");
long time = rs.getLong("time");
String model = rs.getString("model");
LiveWebDanmuBean.Danmu danmu = new LiveWebDanmuBean.Danmu();
danmu.setText(text);
danmu.setFontSize(fontSize);
danmu.setColor("#" + color);
danmu.setTime(time);
danmu.setBarrageType(model);
list.add(danmu);
}
rs.close();
} catch (SQLException e) {
Log.e(e);
}
return list;
}
@Override
public <T extends AbsDatabasesBean> List<T> get(String table, String where, Class<T> tClass) {
return super.get(table, where, tClass);
}
@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;
} }
@@ -244,8 +295,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.buildConfigTmp("33989"));
biliLiveDatabase.resetSQL(); // biliLiveDatabase.resetSQL();
biliLiveDatabase.resetData(); biliLiveDatabase.resetData();
} }

View File

@@ -15,16 +15,19 @@ import com.yutou.biliapi.bean.websocket.WebSocketBody;
import com.yutou.biliapi.bean.websocket.WebSocketHeader; 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.utils.BiliUserUtils;
import com.yutou.biliapi.utils.BytesUtils; import com.yutou.biliapi.utils.BytesUtils;
import com.yutou.bilibili.services.LiveDatabasesService;
import com.yutou.bilibili.services.LiveService;
import com.yutou.common.okhttp.HttpBody; import com.yutou.common.okhttp.HttpBody;
import com.yutou.common.okhttp.HttpCallback; import com.yutou.common.okhttp.HttpCallback;
import com.yutou.common.utils.ConfigTools; import com.yutou.common.utils.ConfigTools;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import jakarta.annotation.Resource;
import lombok.Setter;
import okhttp3.Headers; import okhttp3.Headers;
import org.java_websocket.client.WebSocketClient; import org.java_websocket.client.WebSocketClient;
import org.java_websocket.handshake.ServerHandshake; import org.java_websocket.handshake.ServerHandshake;
import org.springframework.stereotype.Service;
import retrofit2.Response; import retrofit2.Response;
import java.io.ByteArrayOutputStream; import java.io.ByteArrayOutputStream;
@@ -39,24 +42,19 @@ 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 { @Service
public class WebSocketServer {
ThreadPoolExecutor executor; ThreadPoolExecutor executor;
private static WebSocketManager instance;
Map<LiveRoomConfig, DanmuTask> roomMap; Map<LiveRoomConfig, DanmuTask> roomMap;
private final List<String> userStopList = new ArrayList<>();//手动停止列表 private final List<String> userStopList = new ArrayList<>();//手动停止列表
@Resource
LiveDatabasesService liveDatabasesService;
private WebSocketManager() { private WebSocketServer() {
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() {
if (instance == null) {
instance = new WebSocketManager();
}
return instance;
}
public boolean checkRoom(LiveRoomConfig roomConfig) { public boolean checkRoom(LiveRoomConfig roomConfig) {
return roomMap.containsKey(roomConfig); return roomMap.containsKey(roomConfig);
} }
@@ -72,16 +70,16 @@ public class WebSocketManager {
} }
public void addRoom(LiveRoomConfig roomConfig, boolean isUser) { public void addRoom(LiveRoomConfig roomConfig, boolean isUser) {
if (!isUser && userStopList.contains(roomConfig.getRoomId().toString())) { if (!isUser && userStopList.contains(roomConfig.getRoomId())) {
return; return;
} }
if (checkRoom(roomConfig)) { if (checkRoom(roomConfig)) {
return; return;
} }
if (isUser) { if (isUser) {
userStopList.remove(roomConfig.getRoomId().toString()); userStopList.remove(roomConfig.getRoomId());
} }
DanmuTask task = new DanmuTask(roomConfig); DanmuTask task = new DanmuTask(roomConfig, isUser);
roomMap.put(roomConfig, task); roomMap.put(roomConfig, task);
Log.i("添加websocket任务"); Log.i("添加websocket任务");
executor.execute(task); executor.execute(task);
@@ -89,23 +87,33 @@ public class WebSocketManager {
public void stopRoom(String roomId, boolean isUser) { public void stopRoom(String roomId, boolean isUser) {
LiveRoomConfig roomConfig = new LiveRoomConfig(); LiveRoomConfig roomConfig = new LiveRoomConfig();
roomConfig.setRoomId(new String(roomId)); roomConfig.setRoomId(roomId);
if (checkRoom(roomConfig)) { if (checkRoom(roomConfig)) {
roomMap.get(roomConfig).close(); roomMap.get(roomConfig).close();
roomMap.remove(roomConfig); roomMap.remove(roomConfig);
} }
if (isUser) { if (isUser) {
userStopList.add(roomConfig.getRoomId().toString()); userStopList.add(roomConfig.getRoomId());
} }
} }
private static class DanmuTask implements Runnable { public void removeUserStopList(String roomId) {
userStopList.remove(roomId);
}
public boolean isUserStopList(String roomId) {
return userStopList.contains(roomId);
}
private class DanmuTask implements Runnable {
LiveRoomConfig roomConfig; LiveRoomConfig roomConfig;
WebSocketClientTh client; WebSocketClientTh client;
boolean isUser;
public DanmuTask(LiveRoomConfig config) { public DanmuTask(LiveRoomConfig config, boolean isUser) {
this.roomConfig = config; this.roomConfig = config;
WebSocketManager.getInstance().roomMap.put(roomConfig, this); this.isUser = isUser;
roomMap.put(roomConfig, this);
} }
@Override @Override
@@ -113,12 +121,12 @@ public class WebSocketManager {
LiveApi api = BiliLiveNetApiManager.getInstance().getApi(roomConfig.getLoginUid()); LiveApi api = BiliLiveNetApiManager.getInstance().getApi(roomConfig.getLoginUid());
Response<HttpBody<LiveRoomInfo>> execute = null; Response<HttpBody<LiveRoomInfo>> execute = null;
try { try {
execute = api.getRoomInfo(roomConfig.getRoomId().toString()).execute(); execute = api.getRoomInfo(roomConfig.getRoomId()).execute();
if (execute.isSuccessful()) { if (execute.isSuccessful()) {
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); 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>() {
@@ -130,9 +138,9 @@ public class WebSocketManager {
// url="ws://127.0.0.1:8765"; // url="ws://127.0.0.1:8765";
try { try {
roomConfig.setLiveInfo(response); roomConfig.setLiveInfo(response);
client = new WebSocketClientTh(new URI(url), roomConfig); client = new WebSocketClientTh(new URI(url), roomConfig, isUser);
} catch (URISyntaxException e) { } catch (URISyntaxException e) {
WebSocketManager.getInstance().roomMap.remove(roomConfig); roomMap.remove(roomConfig);
throw new RuntimeException(e); throw new RuntimeException(e);
} }
} }
@@ -140,7 +148,7 @@ public class WebSocketManager {
@Override @Override
public void onFailure(Throwable throwable) { public void onFailure(Throwable throwable) {
WebSocketManager.getInstance().roomMap.remove(roomConfig); roomMap.remove(roomConfig);
Log.e(throwable); Log.e(throwable);
} }
}); });
@@ -151,20 +159,21 @@ public class WebSocketManager {
} }
} }
private static class WebSocketClientTh extends WebSocketClient { private class WebSocketClientTh extends WebSocketClient {
private LiveRoomConfig roomConfig; private final LiveRoomConfig roomConfig;
private HeartbeatTask heartbeatTask; private final HeartbeatTask heartbeatTask;
BiliLiveDatabase liveDatabase; private final boolean isUser;
private boolean itTmp = true; private final String logTag;
public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId) { public WebSocketClientTh(URI serverUri, LiveRoomConfig roomId, boolean isUser) {
super(serverUri); super(serverUri);
Log.i("WebSocketClientTh.WebSocketClientTh : " + serverUri); logTag = "WebSocket-" + roomId.getRoomId();
Log.getDynamicLogger(logTag).info("WebSocketClientTh.WebSocketClientTh = {}", serverUri);
this.isUser = isUser;
this.roomConfig = roomId; this.roomConfig = roomId;
liveDatabase = new BiliLiveDatabase(roomConfig);
Brotli4jLoader.ensureAvailability(); Brotli4jLoader.ensureAvailability();
heartbeatTask = new HeartbeatTask();
addHeader("User-Agent", ConfigTools.getUserAgent()); addHeader("User-Agent", ConfigTools.getUserAgent());
heartbeatTask = new HeartbeatTask();
connect(); connect();
} }
@@ -174,7 +183,7 @@ public class WebSocketManager {
heartbeatTask.setSocket(this); heartbeatTask.setSocket(this);
heartbeatTask.sendInitAuthData(); heartbeatTask.sendInitAuthData();
new Timer().schedule(heartbeatTask, 1000, 30000); new Timer().schedule(heartbeatTask, 1000, 30000);
Log.i("WebSocketClientTh.onOpen", roomConfig.getRoomId()); Log.getDynamicLogger(logTag).info("WebSocketClientTh.onOpen,{}", roomConfig.getRoomId());
} }
@Override @Override
@@ -191,18 +200,21 @@ 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.getDynamicLogger(logTag).error("WebSocketClientTh.onClose. {},{},{}", "i = " + i + ", s = " + s + ", b = " + b, roomConfig.getRoomId(), heartbeatTask.socket.isOpen());
WebSocketManager.getInstance().roomMap.remove(roomConfig); roomMap.remove(roomConfig);
liveDatabase.close();
heartbeatTask.cancel(); heartbeatTask.cancel();
if (i == 1006) {
if (LiveService.checkLive(roomConfig.getRoomId())) {
addRoom(roomConfig, isUser);
}
}
} }
@Override @Override
public void onError(Exception e) { public void onError(Exception e) {
Log.i("WebSocketClientTh.onError", roomConfig.getRoomId()); Log.getDynamicLogger(logTag).error("WebSocketClientTh.onError,{}", roomConfig.getRoomId());
Log.e(e); Log.e(e);
WebSocketManager.getInstance().roomMap.remove(roomConfig); roomMap.remove(roomConfig);
liveDatabase.close();
heartbeatTask.cancel(); heartbeatTask.cancel();
} }
@@ -212,23 +224,28 @@ public class WebSocketManager {
* @param data 待压缩的数据 * @param data 待压缩的数据
*/ */
public void decompress(byte[] data) { public void decompress(byte[] data) {
byte[] bytes = new byte[data.length - 16];
WebSocketHeader header = new WebSocketHeader(data);
System.arraycopy(data, header.getHeaderSize(), bytes, 0, data.length - header.getHeaderSize());
// Log.i("数据大小:" + header.getDataSize() + " 协议:" + header.getAgree() + " 头部大小:" + header.getHeaderSize() + " 命令:" + header.getCmdData()); // Log.i("数据大小:" + header.getDataSize() + " 协议:" + header.getAgree() + " 头部大小:" + header.getHeaderSize() + " 命令:" + header.getCmdData());
switch (header.getAgree()) { Thread.ofVirtual()
case 0: .name("TaskUnDanmu-" + Thread.currentThread().getName())
case 1: .start(() -> {
danmu(bytes); byte[] bytes = new byte[data.length - 16];
break; WebSocketHeader header = new WebSocketHeader(data);
default: System.arraycopy(data, header.getHeaderSize(), bytes, 0, data.length - header.getHeaderSize());
unzipDanmu(bytes, header.getAgree() == 3); switch (header.getAgree()) {
} case 0:
case 1:
danmu(bytes);
break;
default:
unzipDanmu(bytes, header.getAgree() == 3);
}
});
} }
private void danmu(byte[] bytes) { private void danmu(byte[] bytes) {
//Log.i("未压缩:" + new String(bytes)); Log.getDynamicLogger(logTag).info("未压缩:{}", new String(bytes));
} }
private void unzipDanmu(byte[] bytes, boolean useHeader) { private void unzipDanmu(byte[] bytes, boolean useHeader) {
@@ -237,29 +254,24 @@ public class WebSocketManager {
DirectDecompress directDecompress = Decoder.decompress(bytes); DirectDecompress directDecompress = Decoder.decompress(bytes);
if (directDecompress.getResultStatus() == DecoderJNI.Status.DONE) { if (directDecompress.getResultStatus() == DecoderJNI.Status.DONE) {
WebSocketBody body = new WebSocketBody(directDecompress.getDecompressedData()); WebSocketBody body = new WebSocketBody(directDecompress.getDecompressedData());
// Log.i("协议:" + useHeader + " 命令数:" + body.getBodyList().size()); Log.getDynamicLogger(logTag).info("协议:{},命令数:{}", useHeader, body.getBodyList().size());
for (JSONObject json : body.getBodyList()) { for (JSONObject json : body.getBodyList()) {
WSData parse = WSData.parse(json); WSData parse = WSData.parse(json);
liveDatabase.addSource(parse); liveDatabasesService.getLiveDatabase(roomConfig.getRoomId()).addSource(parse);
// Log.i("解压:" + parse); Log.getDynamicLogger(logTag).info("解压:{}", parse);
} }
// Log.i();
// Log.i();
} else { } else {
Log.e(new RuntimeException("解压失败")); Log.getDynamicLogger(logTag).error(new RuntimeException("解压失败"));
} }
} catch (Exception e) { } catch (Exception e) {
Log.e(e); Log.getDynamicLogger(logTag).error(e);
} }
} }
@Setter
private class HeartbeatTask extends TimerTask { private class HeartbeatTask extends TimerTask {
WebSocketClientTh socket; WebSocketClientTh socket;
public void setSocket(WebSocketClientTh socket) {
this.socket = socket;
}
@Override @Override
public void run() { public void run() {
try { try {
@@ -284,31 +296,26 @@ public class WebSocketManager {
json.put("uid", 0); json.put("uid", 0);
} }
LoginCookieDatabaseBean cookie = BiliBiliLoginDatabase.getInstance().getCookie(roomConfig.getLoginUid()); LoginCookieDatabaseBean cookie = BiliBiliLoginDatabase.getInstance().getCookie(roomConfig.getLoginUid());
Log.d("cookie:", cookie, "RoomId:" + roomConfig); try {
String buvid = BiliUserUtils.getBuvid(cookie); json.put("roomid", new BigInteger(roomConfig.getRoomId()));
if (buvid != null) { json.put("protover", 3);
try { // json.put("buvid3", BiliUserUtils.getBuvid(roomConfig.isLogin() ? BiliBiliLoginDatabase.getInstance().getCookie(roomConfig.getRoomId()) : null));
json.put("roomid", new BigInteger(roomConfig.getRoomId())); json.put("platform", "web");
json.put("protover", 3); json.put("type", 2);
json.put("buvid", buvid); json.put("key", roomConfig.getLiveInfo().getToken());
json.put("platform", "web"); byte[] bytes = {0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1};
json.put("type", 2); ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
json.put("key", roomConfig.getLiveInfo().getToken());
byte[] bytes = {0, 16, 0, 1, 0, 0, 0, 7, 0, 0, 0, 1};
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
// Log.i("bytes.length = " + bytes.length); // Log.i("bytes.length = " + bytes.length);
Log.i(json); Log.i(json);
outputStream.write(BytesUtils.toLH(json.toString().length() + 16)); outputStream.write(BytesUtils.toLH(json.toString().length() + 16));
outputStream.write(bytes); outputStream.write(bytes);
outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8)); outputStream.write(json.toJSONString().getBytes(StandardCharsets.UTF_8));
outputStream.flush(); outputStream.flush();
// 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

@@ -2,9 +2,11 @@ package com.yutou.bilibili.Controllers;
import com.alibaba.fastjson2.JSONArray; import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.yutou.biliapi.bean.live.FollowLive;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean; import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.bean.login.LoginUserDatabaseBean; import com.yutou.biliapi.bean.login.LoginUserDatabaseBean;
import com.yutou.biliapi.bean.user.UserFollowingsBean; import com.yutou.biliapi.bean.user.UserFollowingsBean;
import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.Tools.Tools; import com.yutou.bilibili.Tools.Tools;
import com.yutou.bilibili.datas.ResultData; import com.yutou.bilibili.datas.ResultData;
import com.yutou.bilibili.datas.ReturnCode; import com.yutou.bilibili.datas.ReturnCode;
@@ -12,18 +14,17 @@ 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;
import org.springframework.util.StringUtils; import org.springframework.util.StringUtils;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod; import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.ArrayList; import java.util.*;
import java.util.Arrays; import java.util.stream.Collectors;
import java.util.List;
@Controller @Controller
@RequestMapping("/live/config/") @RequestMapping("/live/config/")
@@ -46,6 +47,17 @@ public class LiveConfigController {
return ResultData.success(bean, bean.getTotal()); return ResultData.success(bean, bean.getTotal());
} }
@ResponseBody
@RequestMapping("followLive")
public JSONObject getFollowLive(String userId) {
List<FollowLive.Room> followLive = userService.getUserFollowLive(userId);
if (followLive == null) {
return ResultData.fail(ReturnCode.RC999);
}
followLive.forEach(item -> item.setLiveTime(DateFormatUtils.getInstance().convertSeconds(Long.parseLong(item.getLiveTime()))));
return ResultData.success(followLive, followLive.size());
}
@ResponseBody @ResponseBody
@RequestMapping("follow/add") @RequestMapping("follow/add")
public JSONObject addFollow(String uid, String anchorId) { public JSONObject addFollow(String uid, String anchorId) {
@@ -79,7 +91,21 @@ public class LiveConfigController {
follow.setUname(jsonObject.getString("uname")); follow.setUname(jsonObject.getString("uname"));
return follow; return follow;
}).toList(); }).toList();
return ResultData.success(userService.followAll(uid,list)); return ResultData.success(userService.followAll(uid, list));
}
@ResponseBody
@RequestMapping("follow/roomId/addList")
public JSONObject addFollowListToRoomId(String array) {
if (!StringUtils.hasText(array)) {
return ResultData.fail(ReturnCode.RC500);
}
JSONArray jsonArray = JSONArray.parseArray(array);
List<LiveConfigDatabaseBean> list = jsonArray.stream().map(o -> {
JSONObject jsonObject = (JSONObject) o;
return configService.addConfig(jsonObject.getString("roomId"), new LiveConfigDatabaseBean());
}).toList();
return ResultData.success(list, list.size());
} }
@ResponseBody @ResponseBody
@@ -110,6 +136,55 @@ public class LiveConfigController {
return ResultData.success(ReturnCode.RC100); return ResultData.success(ReturnCode.RC100);
} }
@RequestMapping(value = "set/array", method = RequestMethod.POST)
@ResponseBody
public JSONObject addArrayConfig(@RequestBody JSONObject jsonObject) {
JSONObject config = jsonObject.getJSONObject("config");
LiveConfigDatabaseBean bean = config.to(LiveConfigDatabaseBean.class);
if (!bean.verifyLiveTimer()) {
return ResultData.fail(ReturnCode.RC999.getCode(), "视频录制时间格式错误");
}
if (!bean.verifyDanmuTimer()) {
return ResultData.fail(ReturnCode.RC999.getCode(), "弹幕录制时间格式错误");
}
if ("on".equals(config.getString("recordDanmu"))) {
bean.setRecordDanmu(true);
}
if ("on".equals(config.getString("recordLive"))) {
bean.setRecordLive(true);
}
if ("on".equals(config.getString("syncDanmuForLive"))) {
bean.setSyncDanmuForLive(true);
}
if (config.containsKey("keywordList")) {
bean.setKeywordList(config.getList("keywordList", String.class));
}
JSONArray jsonArray = jsonObject.getJSONArray("array");
List<LiveConfigDatabaseBean> list = jsonArray.stream().map(roomId -> configService.addConfig(roomId.toString(), bean)).toList();
int countNull = list.stream().filter(Objects::isNull).toList().size();
return ResultData.success("成功配置" + (list.size() - countNull) + "个直播间");
}
@RequestMapping(value = "delete/array", method = RequestMethod.POST)
@ResponseBody
public JSONObject deleteArrayConfig(@RequestBody JSONArray jsonArray) {
List<Boolean> list = jsonArray.stream().map(roomId -> configService.deleteConfig(roomId.toString())).toList();
int countNull = list.stream().filter(it -> !it).toList().size();
return ResultData.success("成功删除" + (list.size() - countNull) + "个直播间");
}
@RequestMapping(value = "delete/all", method = RequestMethod.GET)
@ResponseBody
public JSONObject deleteAllConfig() {
for (LiveConfigDatabaseBean bean : configService.getAllConfig()) {
configService.deleteConfig(bean.getRoomId());
}
if (configService.getAllConfig().isEmpty()) {
return ResultData.success("成功删除");
} else {
return ResultData.fail(-1, "删除失败,剩余:" + configService.getAllConfig().size() + "个直播间");
}
}
@RequestMapping(value = "set", method = RequestMethod.POST) @RequestMapping(value = "set", method = RequestMethod.POST)
@ResponseBody @ResponseBody

View File

@@ -1,5 +1,6 @@
package com.yutou.bilibili.Controllers; package com.yutou.bilibili.Controllers;
import com.alibaba.fastjson2.JSON;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.yutou.bilibili.datas.ResultData; import com.yutou.bilibili.datas.ResultData;
import com.yutou.bilibili.services.LiveDanmuService; import com.yutou.bilibili.services.LiveDanmuService;
@@ -10,6 +11,8 @@ import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
import java.util.List;
@Controller @Controller
public class LiveController { public class LiveController {
@Resource @Resource
@@ -22,13 +25,36 @@ public class LiveController {
@RequestMapping("/live/list") @RequestMapping("/live/list")
@ResponseBody @ResponseBody
public JSONObject getLiveList(int page,int limit) { public JSONObject getLiveList(int page, int limit) {
return ResultData.success(liveService.getLiveList(page,limit), liveService.getConfigCount()); List<JSONObject> list = liveService.getLiveList(page, limit).stream().map(it -> {
JSONObject json = JSONObject.parseObject(JSON.toJSONString(it));
if (videoService.isUserStopList(it.getRoomId())) {
json.put("videoListen", "已暂停");
} else if (it.isDownloadVideo()) {
json.put("videoListen", "录制中");
} else {
json.put("videoListen", "待机中");
}
if (danmuService.isUserStopList(it.getRoomId())) {
json.put("danmuListen", "已暂停");
} else if (it.isDanmu()) {
json.put("danmuListen", "录制中");
} else {
json.put("danmuListen", "待机中");
}
return json;
}).toList();
return ResultData.success(list, liveService.getConfigCount());
} }
@RequestMapping("/live/gift/info") @RequestMapping("/live/gift/info")
@ResponseBody @ResponseBody
public JSONObject download(String roomId, String videoId) { public JSONObject info(String roomId, String videoId) {
return ResultData.success(liveService.getGiftInfo(roomId,videoId)); return ResultData.success(liveService.getGiftInfo(roomId, videoId));
}
@RequestMapping("/live/gift/info/item")
@ResponseBody
public JSONObject infoItem(String roomId,String videoId,String giftName) {
return ResultData.success(liveService.getGiftItemInfo(roomId, videoId, giftName));
} }
} }

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

@@ -5,6 +5,7 @@ import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.databases.BiliLiveConfigDatabase; import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
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.LiveDatabasesService;
import com.yutou.bilibili.services.LiveVideoDownloadService; import com.yutou.bilibili.services.LiveVideoDownloadService;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
@@ -17,6 +18,8 @@ import java.util.List;
public class LiveVideoController { public class LiveVideoController {
@Resource @Resource
LiveVideoDownloadService videoService; LiveVideoDownloadService videoService;
@Resource
LiveDatabasesService databasesService;
@RequestMapping("/live/video/list") @RequestMapping("/live/video/list")
@ResponseBody @ResponseBody
@@ -34,9 +37,7 @@ public class LiveVideoController {
@RequestMapping("/live/video/start") @RequestMapping("/live/video/start")
@ResponseBody @ResponseBody
public JSONObject startDownload(String roomId) { public JSONObject startDownload(String roomId) {
BiliLiveConfigDatabase liveConfigDatabase = new BiliLiveConfigDatabase(); List<LiveConfigDatabaseBean> list = databasesService.getConfigDatabase().getAllConfig();
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
liveConfigDatabase.close();
for (LiveConfigDatabaseBean bean : list) { for (LiveConfigDatabaseBean bean : list) {
if (bean.getRoomId().toString().equals(roomId)) { if (bean.getRoomId().toString().equals(roomId)) {
videoService.start(bean, true); videoService.start(bean, true);

View File

@@ -1,17 +1,26 @@
package com.yutou.bilibili.Controllers; package com.yutou.bilibili.Controllers;
import com.yutou.bilibili.services.LiveDanmuService;
import com.yutou.bilibili.services.LiveDatabasesService;
import com.yutou.bilibili.services.LiveVideoDownloadService;
import com.yutou.common.okhttp.HttpLoggingInterceptor; import com.yutou.common.okhttp.HttpLoggingInterceptor;
import com.yutou.common.utils.FFmpegUtils; import jakarta.annotation.Resource;
import org.springframework.stereotype.Controller; import org.springframework.stereotype.Controller;
import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.ResponseBody; import org.springframework.web.bind.annotation.ResponseBody;
@Controller @Controller
public class TestControllers { public class TestControllers {
@RequestMapping("/root/all") @Resource
LiveDatabasesService databasesService;
@Resource
LiveDanmuService danmuService;
@Resource
LiveVideoDownloadService videoDownloadService;
@RequestMapping("/test/database")
@ResponseBody @ResponseBody
public String test(){ public String test(){
return "hello world"; return "缓存:"+databasesService.getCacheInfo()+", 弹幕数:"+danmuService.getLiveRoomList().size()+", 视频数:"+videoDownloadService.getDownloadTasks().size();
} }
@ResponseBody @ResponseBody
@RequestMapping("/root/log") @RequestMapping("/root/log")

View File

@@ -25,6 +25,7 @@ import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.net.URL; import java.net.URL;
import java.net.URLDecoder; import java.net.URLDecoder;
import java.net.URLEncoder;
import java.nio.charset.StandardCharsets; import java.nio.charset.StandardCharsets;
import java.util.Base64; import java.util.Base64;
import java.util.List; import java.util.List;
@@ -57,7 +58,7 @@ public class VideoFileController {
try { try {
length = new URL(url).openConnection().getContentLength(); length = new URL(url).openConnection().getContentLength();
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); return Tools.getFile(new File("Web"+ File.separator+"assets"+File.separator+"def.png"));
} }
if (img.exists()&&length==img.length()) { if (img.exists()&&length==img.length()) {
return Tools.getFile(img); return Tools.getFile(img);
@@ -109,6 +110,13 @@ public class VideoFileController {
@RequestMapping("/video/play") @RequestMapping("/video/play")
@ResponseBody @ResponseBody
public JSONObject getVideoUrl(String roomId, String videoId) { public JSONObject getVideoUrl(String roomId, String videoId) {
return ResultData.success("/play/"+videoService.getVideoPlay(roomId, videoId)); String url = videoService.getVideoPlay(roomId, videoId);
/* String[] split = url.split("/");
StringBuilder sb=new StringBuilder();
for (String s : split) {
sb.append(URLEncoder.encode(s,StandardCharsets.UTF_8)).append("/");
}
sb.setLength(sb.length()-1);*/
return ResultData.success("/live"+url);
} }
} }

View File

@@ -1,9 +1,11 @@
package com.yutou.bilibili.Tools; package com.yutou.bilibili.Tools;
import com.yutou.bilibili.services.LiveDatabasesService;
import com.yutou.bilibili.services.LiveVideoDownloadService; import com.yutou.bilibili.services.LiveVideoDownloadService;
import com.yutou.bilibili.services.SystemService; import com.yutou.bilibili.services.SystemService;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.jetbrains.annotations.NotNull;
import org.springframework.context.ApplicationListener; import org.springframework.context.ApplicationListener;
import org.springframework.context.event.ContextClosedEvent; import org.springframework.context.event.ContextClosedEvent;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
@@ -14,10 +16,14 @@ public class ApplicationClose implements ApplicationListener<ContextClosedEvent>
SystemService systemConfigService; SystemService systemConfigService;
@Resource @Resource
LiveVideoDownloadService videoService; LiveVideoDownloadService videoService;
@Resource
LiveDatabasesService databasesService;
@Override @Override
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) { public void onApplicationEvent(@NotNull ContextClosedEvent contextClosedEvent) {
Log.i("服务结束"); Log.i("服务结束");
systemConfigService.stop(); systemConfigService.stop();
videoService.stopAll(); videoService.stopAll();
databasesService.closeAll();
} }
} }

View File

@@ -1,13 +1,17 @@
package com.yutou.bilibili.Tools; package com.yutou.bilibili.Tools;
import com.yutou.biliapi.bean.live.LiveRoomInfo;
import com.yutou.bilibili.services.SystemService; import com.yutou.bilibili.services.SystemService;
import com.yutou.common.utils.DynamicLogFile;
import com.yutou.common.utils.Log;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Logger;
import org.springframework.boot.ApplicationArguments; import org.springframework.boot.ApplicationArguments;
import org.springframework.boot.ApplicationRunner; import org.springframework.boot.ApplicationRunner;
import org.springframework.stereotype.Component; import org.springframework.stereotype.Component;
import java.util.logging.Level; import java.util.logging.Level;
import java.util.logging.Logger;
/** /**
* 服务启动后执行 * 服务启动后执行
@@ -16,11 +20,11 @@ import java.util.logging.Logger;
public class ApplicationInit implements ApplicationRunner { public class ApplicationInit implements ApplicationRunner {
@Resource @Resource
SystemService systemConfigService; SystemService systemConfigService;
Logger logger = LogManager.getLogger(ApplicationInit.class);
@Override @Override
public void run(ApplicationArguments args) throws Exception { public void run(ApplicationArguments args) throws Exception {
Logger logger = Logger.getLogger("ApplicationInit"); logger.info("服务启动后执行");
logger.log(Level.INFO, "服务启动后执行");
systemConfigService.start(); systemConfigService.start();
} }

View File

@@ -0,0 +1,22 @@
package com.yutou.bilibili.Tools;
import com.yutou.bilibili.services.LiveDanmuService;
import com.yutou.bilibili.services.LiveVideoDownloadService;
import jakarta.annotation.Resource;
import org.springframework.scheduling.annotation.EnableScheduling;
import org.springframework.scheduling.annotation.Scheduled;
import org.springframework.stereotype.Component;
@Component
@EnableScheduling
public class ApplicationTime {
@Resource
LiveVideoDownloadService videoService;
@Resource
LiveDanmuService danmuService;
@Scheduled(cron = "1 0 0 * * *")
public void reset(){
videoService.clearUserStopList();
danmuService.clearUserList();
}
}

View File

@@ -76,6 +76,17 @@ public class DateFormatUtils {
Date time = parse(date, format); Date time = parse(date, format);
return format(time, format); return format(time, format);
} }
public String convertSeconds(long totalSeconds) {
// 计算总小时数
long hours = totalSeconds / 3600;
// 剩余的秒数
long remainingSecondsAfterHours = totalSeconds % 3600;
// 计算分钟数
long minutes = remainingSecondsAfterHours / 60;
// 最后剩余的秒数
long seconds = remainingSecondsAfterHours % 60;
return String.format("%d小时%d分%d秒", hours, minutes, seconds);
}
public String formatMillis(long millis) { public String formatMillis(long millis) {
Duration duration = Duration.ofMillis(millis); Duration duration = Duration.ofMillis(millis);
int seconds = (int) (duration.getSeconds() % 60); int seconds = (int) (duration.getSeconds() % 60);

View File

@@ -22,7 +22,7 @@ import java.io.File;
public class LiveInfoNfoTools { public class LiveInfoNfoTools {
public static void saveLiveInfoNfo(LiveRoomInfo info, String path,String xmlName) { public static void saveLiveInfoNfo(LiveRoomInfo info, String path,String xmlName) {
try { try {
LiveRoomConfig config = LiveRoomConfig.buildConfig(info.getRoomId().toString()); LiveRoomConfig config = LiveRoomConfig.buildConfigTmp(info.getRoomId());
DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance(); DocumentBuilderFactory dbFactory = DocumentBuilderFactory.newInstance();
DocumentBuilder dBuilder = null; DocumentBuilder dBuilder = null;
dBuilder = dbFactory.newDocumentBuilder(); dBuilder = dbFactory.newDocumentBuilder();

View File

@@ -1,66 +0,0 @@
package com.yutou.bilibili.datas.web;
import com.yutou.biliapi.bean.live.database.LiveSuperChatDatabaseBean;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class LiveVideoDanmu {
List<Danmu> danmu = new ArrayList<>();
List<SuperChat> superChat = new ArrayList<>();
@Data
public static class Addition {
private int grade = 2;
}
@Data
public static class Danmu {
public static final String DANMU_TYPE_SCROLL = "scroll";
public static final String DANMU_TYPE_TOP = "top";
public static final String DANMU_TYPE_BOTTOM = "bottom";
private String id;
private String barrageType = DANMU_TYPE_SCROLL;
private long time;
private String text;
private int fontSize = 25;
private int lineHeight = 1;
private String color = "#ffffff";
private Addition addition = new Addition();
}
@Data
public static class SuperChat {
private int id;
private long price;
private String uid;
private long start_time;
private long duration;
private String message;
private String message_trans;
private String message_font_color;
private String backgroundBottomColor;
private String userName;
private String userNameColor;
private String userAvatar;
public SuperChat(long videoTime, LiveSuperChatDatabaseBean bean) {
this.id = bean.getId();
this.price = bean.getPrice();
this.uid = bean.getUid();
this.start_time = (bean.getSql_time().getTime() - videoTime) / 1000;
this.duration = bean.getEnd_time() - bean.getStart_time();
this.message = bean.getMessage();
this.message_trans = bean.getMessage_trans();
this.message_font_color = bean.getMessage_font_color();
this.backgroundBottomColor = bean.getBackgroundBottomColor();
this.userName = bean.getUserName();
this.userAvatar = bean.getUserAvatar();
this.userNameColor=bean.getUserNameColor();
}
}
}

View File

@@ -0,0 +1,36 @@
package com.yutou.bilibili.datas.web;
import lombok.Data;
import java.util.ArrayList;
import java.util.List;
@Data
public class LiveWebDanmuBean {
List<Danmu> danmu = new ArrayList<>();
boolean isNextDanmu = false;
long danmuCount = 0;
@Data
public static class Addition {
private int grade = 2;
}
@Data
public static class Danmu {
public static final String DANMU_TYPE_SCROLL = "scroll";
public static final String DANMU_TYPE_TOP = "top";
public static final String DANMU_TYPE_BOTTOM = "bottom";
private String id;
private String barrageType = DANMU_TYPE_SCROLL;
private long time;
private String text;
private int fontSize = 25;
private int lineHeight = 1;
private String color = "#ffffff";
private Addition addition = new Addition();
}
}

View File

@@ -0,0 +1,35 @@
package com.yutou.bilibili.datas.web;
import com.yutou.biliapi.bean.live.database.LiveSuperChatDatabaseBean;
import lombok.Data;
@Data
public class SuperChatGiftBean {
private int id;
private long price;
private String uid;
private long start_time;
private long duration;
private String message;
private String message_trans;
private String message_font_color;
private String backgroundBottomColor;
private String userName;
private String userNameColor;
private String userAvatar;
public SuperChatGiftBean(long videoTime, LiveSuperChatDatabaseBean bean) {
this.id = bean.getId();
this.price = bean.getPrice();
this.uid = bean.getUid();
this.start_time = (bean.getSql_time().getTime() - videoTime) / 1000;
this.duration = bean.getEnd_time() - bean.getStart_time();
this.message = bean.getMessage();
this.message_trans = bean.getMessage_trans();
this.message_font_color = bean.getMessage_font_color();
this.backgroundBottomColor = bean.getBackgroundBottomColor();
this.userName = bean.getUserName();
this.userAvatar = bean.getUserAvatar();
this.userNameColor = bean.getUserNameColor();
}
}

View File

@@ -17,7 +17,12 @@ import java.util.List;
@Service @Service
public class LiveConfigService { public class LiveConfigService {
BiliLiveConfigDatabase database = new BiliLiveConfigDatabase(); @Resource
LiveDatabasesService databasesService;
@Resource
LiveDanmuService danmuService;
@Resource
LiveVideoDownloadService videoDownloadService;
public LiveConfigDatabaseBean addConfig(String url, LiveConfigDatabaseBean bean) { public LiveConfigDatabaseBean addConfig(String url, LiveConfigDatabaseBean bean) {
if (!StringUtils.hasText(url)) { if (!StringUtils.hasText(url)) {
@@ -35,7 +40,9 @@ public class LiveConfigService {
bean.setRoomId(body.getRoomId()); bean.setRoomId(body.getRoomId());
bean.setAnchorFace(infoBean.getInfo().getFace()); bean.setAnchorFace(infoBean.getInfo().getFace());
bean.setAnchorName(infoBean.getInfo().getUname()); bean.setAnchorName(infoBean.getInfo().getUname());
database.setConfig(bean); databasesService.getConfigDatabase().setConfig(bean);
danmuService.removeUserStopList(roomId);
videoDownloadService.removeUserStopList(roomId);
return bean; return bean;
} catch (IOException e) { } catch (IOException e) {
throw new RuntimeException(e); throw new RuntimeException(e);
@@ -43,38 +50,39 @@ public class LiveConfigService {
} }
public LiveConfigDatabaseBean updateConfig(String roomId, LiveConfigDatabaseBean bean) { public LiveConfigDatabaseBean updateConfig(String roomId, LiveConfigDatabaseBean bean) {
LiveConfigDatabaseBean config = database.getConfig(roomId); LiveConfigDatabaseBean config = databasesService.getConfigDatabase().getConfig(roomId);
if (config == null) { if (config == null) {
return null; return null;
} }
bean.setRoomId(roomId); bean.setRoomId(roomId);
bean.setSql_time(config.getSql_time()); bean.setSql_time(config.getSql_time());
database.setConfig(bean); databasesService.getConfigDatabase().setConfig(bean);
return bean; return bean;
} }
public boolean deleteConfig(String roomId) { public boolean deleteConfig(String roomId) {
LiveConfigDatabaseBean config = database.getConfig(roomId); LiveConfigDatabaseBean config = databasesService.getConfigDatabase().getConfig(roomId);
if (config == null) { if (config == null) {
return false; return false;
} }
return database.deleteConfig(roomId); return databasesService.getConfigDatabase().deleteConfig(roomId);
} }
public List<LiveConfigDatabaseBean> getAllConfig() { public List<LiveConfigDatabaseBean> getAllConfig() {
return database.getAllConfig(); return databasesService.getConfigDatabase().getAllConfig();
} }
public List<LiveConfigDatabaseBean> getConfigs(int page,int limit) {
return database.getConfigs(page, limit); public List<LiveConfigDatabaseBean> getConfigs(int page, int limit) {
return databasesService.getConfigDatabase().getConfigs(page, limit);
} }
public LiveConfigDatabaseBean getConfig(String roomId) { public LiveConfigDatabaseBean getConfig(String roomId) {
return database.getConfig(roomId); return databasesService.getConfigDatabase().getConfig(roomId);
} }
public File getFace(String roomId) { public File getFace(String roomId) {
LiveConfigDatabaseBean config = database.getConfig(new String(roomId)); LiveConfigDatabaseBean config = databasesService.getConfigDatabase().getConfig(new String(roomId));
if (config == null) { if (config == null) {
return null; return null;
} }
@@ -102,6 +110,6 @@ public class LiveConfigService {
} }
public int getConfigCount() { public int getConfigCount() {
return database.getConfigCount(); return databasesService.getConfigDatabase().getConfigCount();
} }
} }

View File

@@ -5,72 +5,82 @@ import com.alibaba.fastjson2.JSONObject;
import com.yutou.biliapi.bean.live.LiveRoomConfig; import com.yutou.biliapi.bean.live.LiveRoomConfig;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean; import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.bean.live.database.LiveDanmuDatabaseBean; import com.yutou.biliapi.bean.live.database.LiveDanmuDatabaseBean;
import com.yutou.biliapi.bean.live.database.LiveSuperChatDatabaseBean;
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean; 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.BiliLiveDatabase; import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.biliapi.net.WebSocketManager; import com.yutou.biliapi.net.WebSocketServer;
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.LiveWebDanmuBean;
import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import jakarta.annotation.Resource;
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 {
@Resource
LiveDatabasesService liveDatabasesService;
@Resource
WebSocketServer webSocketServer;
public void start(String roomId, boolean isUser) { public void start(String roomId, boolean isUser) {
WebSocketManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId), isUser); webSocketServer.addRoom(liveDatabasesService.buildConfig(roomId), isUser);
}
public void start(LiveConfigDatabaseBean roomId, boolean isUser) {
webSocketServer.addRoom(liveDatabasesService.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 webSocketServer.checkRoom(roomConfig);
} }
public void stop(String roomId, boolean isUser) { public void stop(String roomId, boolean isUser) {
WebSocketManager.getInstance().stopRoom(roomId, isUser); webSocketServer.stopRoom(roomId, isUser);
} }
public JSONArray getLiveRoomList() { public JSONArray getLiveRoomList() {
return WebSocketManager.getInstance().getLiveRoomList(); return webSocketServer.getLiveRoomList();
} }
public void clearUserList() { public void clearUserList() {
WebSocketManager.getInstance().clearUserStopList(); webSocketServer.clearUserStopList();
}
public void removeUserStopList(String roomId) {
webSocketServer.removeUserStopList(roomId);
}
public boolean isUserStopList(String roomId) {
return webSocketServer.isUserStopList(roomId);
} }
public List<File> getDanmuFileList(String roomId) { public List<File> getDanmuFileList(String roomId) {
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase(); LiveConfigDatabaseBean bean = liveDatabasesService.getConfigDatabase().getConfig(roomId);
LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
configDatabase.close();
return Tools.scanFile(new File(bean.getRecordPath() + File.separator + bean.getAnchorName())); return Tools.scanFile(new File(bean.getRecordPath() + File.separator + bean.getAnchorName()));
} }
public void saveDanmuXML(LiveVideoDatabaseBean videoDatabaseBean, BiliLiveDatabase database) { public void saveDanmuXML(LiveVideoDatabaseBean videoDatabaseBean, BiliLiveDatabase database) {
File videoFile = new File(videoDatabaseBean.getPath()); File videoFile = new File(videoDatabaseBean.getPath());
long videoTime = FFmpegUtils.getVideoTime(videoFile) + videoDatabaseBean.getStartTime().getTime(); long videoTime;
if (videoDatabaseBean.getStopTime() == null) {
videoTime = System.currentTimeMillis();
} else {
videoTime = videoDatabaseBean.getStopTime().getTime();
}
Log.i("开始时间:" + videoDatabaseBean.getStartTime().getTime()); Log.i("开始时间:" + videoDatabaseBean.getStartTime().getTime());
Log.i("结束时间:" + videoTime); Log.i("结束时间:" + videoTime);
String startTime = DateFormatUtils.getInstance().format(videoDatabaseBean.getStartTime()); List<LiveDanmuDatabaseBean> danmus = database.getOfTime(videoDatabaseBean.getStartTime().getTime(), videoTime, LiveDanmuDatabaseBean.class);
String endTime = DateFormatUtils.getInstance().format(videoTime);
List<LiveDanmuDatabaseBean> danmus = database.getOfTime(startTime, endTime, LiveDanmuDatabaseBean.class);
Log.i("弹幕数量:" + danmus.size()); Log.i("弹幕数量:" + danmus.size());
AssTools assTools = new AssTools(videoFile.getName().replace(".flv", ""), videoDatabaseBean.getStartTime()); AssTools assTools = new AssTools(videoFile.getName().replace(".m3u8", ""), videoDatabaseBean.getStartTime());
for (LiveDanmuDatabaseBean dm : danmus) { for (LiveDanmuDatabaseBean dm : danmus) {
assTools.addDanmu(dm.createDanmuData()); assTools.addDanmu(dm.createDanmuData());
} }
assTools.saveDanmu(videoFile.getAbsolutePath().replace(".flv", ".ass")); assTools.saveDanmu(videoFile.getAbsolutePath().replace(".m3u8", ".ass"));
} }
public String toTimeString(long videoTime) { public String toTimeString(long videoTime) {
@@ -83,59 +93,55 @@ 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 LiveWebDanmuBean getDanmu(String roomId, String videoId, int page) {
LiveVideoDanmu danmus = new LiveVideoDanmu(); LiveWebDanmuBean danmus = new LiveWebDanmuBean();
BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId)); BiliLiveDatabase liveDatabase = liveDatabasesService.getLiveDatabase(roomId);
LiveVideoDatabaseBean videoBean = liveDatabase.getVideo(videoId); try {
if (videoBean == null) { LiveVideoDatabaseBean videoBean = liveDatabase.getVideo(videoId);
return new LiveVideoDanmu(); if (videoBean == null) {
} return new LiveWebDanmuBean();
File videoFile = new File(videoBean.getPath().replace(".flv", ".mp4")); }
if (!videoFile.exists()) { long startTime = videoBean.getStartTime().getTime();
videoFile = new File(videoBean.getPath()); long endTime = videoBean.getStopTime() == null ? System.currentTimeMillis() : videoBean.getStopTime().getTime();
}
long videoTime = FFmpegUtils.getVideoTime(videoFile); long count = liveDatabase.getCount(new LiveDanmuDatabaseBean().getTableName());
long startTime = Long.parseLong(videoId); int pageSize = 3000;
long endTime = Long.parseLong(videoId) + videoTime; int pageCount = (int) Math.ceil((double) count / pageSize);
// videoTime = 0;
if (videoTime == 0) { List<LiveWebDanmuBean.Danmu> danmuList = liveDatabase.getDanmu(startTime, endTime, page, pageSize);
endTime = System.currentTimeMillis(); danmus.getDanmu().addAll(danmuList);
} danmus.setDanmuCount(count);
List<LiveDanmuDatabaseBean> danmuList = liveDatabase.getOfTime(DateFormatUtils.getInstance().format(startTime), DateFormatUtils.getInstance().format(endTime), LiveDanmuDatabaseBean.class); if (page < pageCount) {
List<LiveSuperChatDatabaseBean> superChatList = liveDatabase.getOfTime(DateFormatUtils.getInstance().format(startTime), DateFormatUtils.getInstance().format(endTime), LiveSuperChatDatabaseBean.class); danmus.setNextDanmu(true);
for (LiveDanmuDatabaseBean bean : danmuList) { }
LiveVideoDanmu.Danmu danmu = createDanmu(bean, startTime);
danmus.getDanmu().add(danmu); } catch (Exception e) {
} Log.e(e);
for (LiveSuperChatDatabaseBean bean : superChatList) {
LiveVideoDanmu.SuperChat superChat = new LiveVideoDanmu.SuperChat(startTime, bean);
danmus.getSuperChat().add(superChat);
} }
liveDatabase.close();
return danmus; return danmus;
} }
@NotNull @NotNull
private static LiveVideoDanmu.Danmu createDanmu(LiveDanmuDatabaseBean bean, long startTime) { private static LiveWebDanmuBean.Danmu createDanmu(LiveDanmuDatabaseBean bean, long startTime) {
LiveVideoDanmu.Danmu danmu = new LiveVideoDanmu.Danmu(); LiveWebDanmuBean.Danmu danmu = new LiveWebDanmuBean.Danmu();
danmu.setId(bean.getId() + ""); danmu.setId(bean.getId() + "");
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(LiveWebDanmuBean.Danmu.DANMU_TYPE_SCROLL);
} else if (bean.getModel() == 4) { } else if (bean.getModel() == 4) {
danmu.setBarrageType(LiveVideoDanmu.Danmu.DANMU_TYPE_BOTTOM); danmu.setBarrageType(LiveWebDanmuBean.Danmu.DANMU_TYPE_BOTTOM);
} else if (bean.getModel() == 5) { } else if (bean.getModel() == 5) {
danmu.setBarrageType(LiveVideoDanmu.Danmu.DANMU_TYPE_TOP); danmu.setBarrageType(LiveWebDanmuBean.Danmu.DANMU_TYPE_TOP);
} }
return danmu; return danmu;
} }
public static void main(String[] args) { public static void main(String[] args) {
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig("17961")); BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfigTmp("17961"));
for (LiveVideoDatabaseBean info : database.getLiveInfos()) { for (LiveVideoDatabaseBean info : database.getLiveInfos()) {
System.out.println(info); System.out.println(info);
} }

View File

@@ -0,0 +1,71 @@
package com.yutou.bilibili.services;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yutou.biliapi.bean.live.LiveRoomConfig;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.common.utils.Log;
import lombok.Getter;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;
import java.util.Arrays;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@Getter
@Service
public class LiveDatabasesService {
private static final Cache<String, BiliLiveDatabase> liveDatabases = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(2, TimeUnit.MINUTES)
.removalListener(it -> {
if (it.wasEvicted()) {
if (it.getValue() != null) {
((BiliLiveDatabase) it.getValue()).close();
Log.i("缓存到期移除", ((BiliLiveDatabase) it.getValue()).getConfig().getRoomId());
}
}
})
.build();
private final BiliLiveConfigDatabase configDatabase;
private LiveDatabasesService() {
configDatabase = new BiliLiveConfigDatabase();
}
public String getCacheInfo() {
return "总数:" + liveDatabases.size() + "," + Arrays.toString(liveDatabases.asMap().keySet().toArray()) + "," + Arrays.toString(liveDatabases.asMap().values().toArray());
}
public BiliLiveDatabase getLiveDatabase(String roomId) {
try {
return liveDatabases.get(roomId, () -> new BiliLiveDatabase(buildConfig(roomId)));
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
public void closeAll() {
try {
for (BiliLiveDatabase db : liveDatabases.asMap().values()) {
db.close();
}
} finally {
liveDatabases.invalidateAll();
configDatabase.close();
}
}
public LiveRoomConfig buildConfig(String roomId) {
LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
LiveRoomConfig config = new LiveRoomConfig();
config.setLoginUid(bean.getRecordUid());
config.setRoomId(bean.getRoomId());
config.setAnchorName(bean.getAnchorName());
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
config.setRootPath(bean.getRecordPath());
return config;
}
}

View File

@@ -4,48 +4,44 @@ import com.alibaba.fastjson2.JSONArray;
import com.alibaba.fastjson2.JSONObject; import com.alibaba.fastjson2.JSONObject;
import com.yutou.biliapi.api.LiveApi; import com.yutou.biliapi.api.LiveApi;
import com.yutou.biliapi.bean.live.LiveAnchorInfo; import com.yutou.biliapi.bean.live.LiveAnchorInfo;
import com.yutou.biliapi.bean.live.LiveRoomConfig;
import com.yutou.biliapi.bean.live.LiveRoomInfo; import com.yutou.biliapi.bean.live.LiveRoomInfo;
import com.yutou.biliapi.bean.live.LiveRoomPlayInfo; import com.yutou.biliapi.bean.live.database.*;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
import com.yutou.biliapi.databases.BiliLiveDatabase; import com.yutou.biliapi.databases.BiliLiveDatabase;
import com.yutou.biliapi.net.BiliLiveNetApiManager; import com.yutou.biliapi.net.BiliLiveNetApiManager;
import com.yutou.bilibili.Tools.DateFormatUtils; import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.bilibili.datas.web.LiveData; import com.yutou.bilibili.datas.web.LiveData;
import com.yutou.common.okhttp.BaseBean; import com.yutou.bilibili.datas.web.LiveWebDanmuBean;
import com.yutou.bilibili.datas.web.SuperChatGiftBean;
import com.yutou.common.okhttp.HttpLoggingInterceptor; import com.yutou.common.okhttp.HttpLoggingInterceptor;
import com.yutou.common.utils.FFmpegUtils;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
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.IOException; import java.io.IOException;
import java.util.*; import java.util.*;
@Service @Service
public class LiveService { public class LiveService {
BiliLiveConfigDatabase liveConfigDatabase;
@Resource @Resource
LiveVideoDownloadService videoDownloadService; LiveVideoDownloadService videoDownloadService;
@Resource @Resource
LiveDanmuService danmuService; LiveDanmuService danmuService;
@Resource
LiveDatabasesService databasesService;
LiveApi api; LiveApi api;
public LiveService() { public LiveService() {
liveConfigDatabase = new BiliLiveConfigDatabase();
api = BiliLiveNetApiManager.getInstance().getApi(null); api = BiliLiveNetApiManager.getInstance().getApi(null);
} }
public int getConfigCount() { public int getConfigCount() {
return liveConfigDatabase.getAllConfig().size(); return databasesService.getConfigDatabase().getAllConfig().size();
} }
public List<LiveData> getLiveList(int page, int limit) { public List<LiveData> getLiveList(int page, int limit) {
List<LiveConfigDatabaseBean> allConfig = liveConfigDatabase.getAllConfig(); List<LiveConfigDatabaseBean> allConfig = databasesService.getConfigDatabase().getAllConfig();
List<LiveData> liveDataList = new ArrayList<>(); List<LiveData> liveDataList = new ArrayList<>();
if (allConfig.isEmpty()) { if (allConfig.isEmpty()) {
return liveDataList; return liveDataList;
@@ -87,7 +83,7 @@ public class LiveService {
if (info.getLiveTime() == 0) { if (info.getLiveTime() == 0) {
liveData.setLiveTime("未开播"); liveData.setLiveTime("未开播");
} else { } else {
liveData.setLiveTime(DateFormatUtils.getInstance().formatMillis(System.currentTimeMillis() - info.getLiveTime()*1000)); liveData.setLiveTime(DateFormatUtils.getInstance().formatMillis(System.currentTimeMillis() - info.getLiveTime() * 1000));
} }
liveData.setDownloadVideo(videoDownloadService.checkDownload(info.getRoomId())); liveData.setDownloadVideo(videoDownloadService.checkDownload(info.getRoomId()));
liveData.setDanmu(danmuService.check(info.getRoomId())); liveData.setDanmu(danmuService.check(info.getRoomId()));
@@ -108,22 +104,63 @@ public class LiveService {
} }
public JSONObject getGiftInfo(String roomId, String videoId) { public JSONObject getGiftInfo(String roomId, String videoId) {
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId)); BiliLiveDatabase database = databasesService.getLiveDatabase(roomId);
LiveVideoDatabaseBean videoBean = database.getVideo(videoId); LiveVideoDatabaseBean videoBean = database.getVideo(videoId);
if (videoBean == null) { if (videoBean == null) {
return null; return null;
} }
File videoFile = new File(videoBean.getPath().replace(".flv", ".mp4")); long startTime = videoBean.getStartTime().getTime();
if (!videoFile.exists()) { long endTime = videoBean.getStopTime() == null ? System.currentTimeMillis() : videoBean.getStopTime().getTime();
videoFile = new File(videoBean.getPath()); List<SuperChatGiftBean> superChatList = database.getOfTime(startTime, endTime, LiveSuperChatDatabaseBean.class)
.stream().map(it -> new SuperChatGiftBean(startTime, it)).toList();
JSONObject giftInfo = database.getGiftInfo(startTime, endTime);
giftInfo.put("superChat", superChatList);
return giftInfo;
}
public JSONArray getGiftItemInfo(String roomId, String videoId, String giftName) {
BiliLiveDatabase database = databasesService.getLiveDatabase(roomId);
LiveVideoDatabaseBean videoBean = database.getVideo(videoId);
if (videoBean == null) {
return new JSONArray();
} }
long videoTime = FFmpegUtils.getVideoTime(videoFile); long startTime = videoBean.getStartTime().getTime();
long startTime = Long.parseLong(videoId); long endTime = videoBean.getStopTime() == null ? System.currentTimeMillis() : videoBean.getStopTime().getTime();
long endTime = Long.parseLong(videoId) + videoTime; String where = " `sql_time` >= " + "\"" + startTime + "\"" +
if(videoTime==0){ " and " + " `sql_time` <= " + "\"" + endTime + "\"" +
endTime=System.currentTimeMillis(); " and " + " `gift_name` = " + "\"" + giftName + "\"";
List<LiveGiftDatabaseBean> list = database.get(new LiveGiftDatabaseBean().getTableName(), where, LiveGiftDatabaseBean.class);
List<LiveGuardBuyBean> list2 = database.get(new LiveGuardBuyBean().getTableName(), where, LiveGuardBuyBean.class);
JSONArray array = new JSONArray();
array.addAll(list);
array.addAll(
list2.stream().map(it -> {
LiveGiftDatabaseBean tmp = new LiveGiftDatabaseBean();
tmp.setGiftId(it.getGiftID());
tmp.setGiftName(it.getGiftName());
tmp.setGiftNum(it.getNum());
tmp.setPrice(it.getPrice());
tmp.setIcon("/assets/def.png");
tmp.setSenderUid(it.getUid());
tmp.setSenderName(it.getUsername());
tmp.setSenderFace("/assets/def.png");
tmp.setSql_time(it.getSql_time());
return tmp;
}).toList()
);
return array;
}
public static boolean checkLive(String roomId) {
try {
LiveRoomInfo data = BiliLiveNetApiManager.getInstance().getApi(null).getRoomInfo(roomId).execute().body().getData();
return data.getLiveStatus() == 1;
} catch (IOException|NullPointerException e) {
Log.e(e);
return false;
} }
return database.getGiftInfo(startTime, endTime);
} }
public static void main(String[] args) { public static void main(String[] args) {
@@ -132,4 +169,6 @@ public class LiveService {
List<LiveData> data = service.getLiveList(1, 16); List<LiveData> data = service.getLiveList(1, 16);
System.out.println(data.size()); System.out.println(data.size());
} }
} }

View File

@@ -1,7 +1,7 @@
package com.yutou.bilibili.services; package com.yutou.bilibili.services;
import com.alibaba.fastjson2.JSONObject;
import com.yutou.biliapi.api.UserApi; import com.yutou.biliapi.api.UserApi;
import com.yutou.biliapi.bean.live.FollowLive;
import com.yutou.biliapi.bean.live.SpiBean; import com.yutou.biliapi.bean.live.SpiBean;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean; import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.biliapi.bean.login.LoginCookieDatabaseBean; import com.yutou.biliapi.bean.login.LoginCookieDatabaseBean;
@@ -9,7 +9,7 @@ import com.yutou.biliapi.bean.user.UserFollowingsBean;
import com.yutou.biliapi.bean.user.UserHomeInfoBean; import com.yutou.biliapi.bean.user.UserHomeInfoBean;
import com.yutou.biliapi.bean.user.UserInfoBean; import com.yutou.biliapi.bean.user.UserInfoBean;
import com.yutou.biliapi.databases.BiliBiliLoginDatabase; import com.yutou.biliapi.databases.BiliBiliLoginDatabase;
import com.yutou.biliapi.net.BiliCookieManager; import com.yutou.biliapi.net.BiliLiveNetApiManager;
import com.yutou.biliapi.net.BiliUserNetApiManager; import com.yutou.biliapi.net.BiliUserNetApiManager;
import com.yutou.biliapi.net.WebSignManager; import com.yutou.biliapi.net.WebSignManager;
import com.yutou.bilibili.datas.ResultData; import com.yutou.bilibili.datas.ResultData;
@@ -34,6 +34,14 @@ public class LiveUserService {
@Resource @Resource
LiveConfigService configService; LiveConfigService configService;
public List<FollowLive.Room> getUserFollowLive(String userId) {
try {
FollowLive followLive = BiliLiveNetApiManager.getInstance().getApi(userId).getUserFollowLive(true, System.currentTimeMillis()).execute().body().getData();
return followLive.getRooms();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
public UserFollowingsBean getUserFollowings(String userId, int page, int num) { public UserFollowingsBean getUserFollowings(String userId, int page, int num) {
LoginCookieDatabaseBean cookie = userLoginService.getCookie(userId); LoginCookieDatabaseBean cookie = userLoginService.getCookie(userId);
UserApi api = BiliUserNetApiManager.getInstance().getUserApi(cookie); UserApi api = BiliUserNetApiManager.getInstance().getUserApi(cookie);

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.WebSocketServer;
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;
@@ -31,14 +29,16 @@ import com.yutou.common.record.AbsVideoRecord;
import com.yutou.common.utils.ConfigTools; 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 jakarta.annotation.Resource;
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;
@@ -49,6 +49,10 @@ public class LiveVideoDownloadService {
private final ThreadPoolExecutor executor; private final ThreadPoolExecutor executor;
private final List<String> userStopList = new ArrayList<>();//手动停止列表 private final List<String> userStopList = new ArrayList<>();//手动停止列表
private final AbsVideoRecord videoRecord; private final AbsVideoRecord videoRecord;
@Resource
LiveDatabasesService liveDatabasesService;
@Resource
WebSocketServer webSocketServer;
public LiveVideoDownloadService() { public LiveVideoDownloadService() {
Log.i("初始化下载服务"); Log.i("初始化下载服务");
@@ -63,21 +67,40 @@ public class LiveVideoDownloadService {
public void clearUserStopList() { public void clearUserStopList() {
userStopList.clear(); userStopList.clear();
} }
public void removeUserStopList(String roomId) {
userStopList.remove(roomId);
}
public boolean isUserStopList(String roomId) {
return userStopList.contains(roomId);
}
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) {
if (bean.getKeywordList()!=null&&!bean.getKeywordList().isEmpty() && !isUser) {
String foundKey = bean.getKeywordList().stream()
.filter(key -> response.getTitle().contains(key))
.findFirst()
.orElse(null);
if (foundKey != null) {
Log.i(response.getRoomId(), "检测到开播关键词", foundKey, response.getTitle());
} else {
Log.i(response.getRoomId(), "未检测到关键词", response.getTitle(), Arrays.toString(bean.getKeywordList().toArray()));
return;
}
}
VideoTask task = new VideoTask(bean, response); VideoTask task = new VideoTask(bean, response);
executor.execute(task); executor.execute(task);
} else { } else {
@@ -119,8 +142,7 @@ public class LiveVideoDownloadService {
String savePath; String savePath;
File rootPath; File rootPath;
LiveConfigDatabaseBean config; LiveConfigDatabaseBean config;
BiliLiveDatabase database; LiveVideoDatabaseBean videoDatabaseBean = null;
LiveVideoDatabaseBean videoDatabaseBean;
LiveRoomInfo roomInfo; LiveRoomInfo roomInfo;
public VideoTask(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) { public VideoTask(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
@@ -134,10 +156,10 @@ public class LiveVideoDownloadService {
public void run() { public void run() {
if (roomInfo.getLiveStatus() == 1) { if (roomInfo.getLiveStatus() == 1) {
String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH); String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH);
rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle()); rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + "[" +
savePath = rootPath.getAbsolutePath() + File.separator + "[" +
DateUtils.format(new Date(), DateUtils.format(new Date(),
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv"; "HH-mm-ss") + "]" + roomInfo.getTitle());
savePath = rootPath.getAbsolutePath() + File.separator + roomInfo.getTitle() + ".m3u8";
if (!rootPath.exists()) { if (!rootPath.exists()) {
rootPath.mkdirs(); rootPath.mkdirs();
} }
@@ -148,14 +170,14 @@ public class LiveVideoDownloadService {
} }
private void stop() { private void stop() {
videoRecord.kill(bean.getRoomId().toString()); videoRecord.kill(bean.getRoomId());
api.getRoomInfo(config.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() { api.getRoomInfo(config.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) {
LiveVideoDownloadService.this.start(bean, false); LiveVideoDownloadService.this.start(bean, false);
} else { } else {
LiveVideoDownloadService.this.stop(bean.getRoomId().toString(), false); LiveVideoDownloadService.this.stop(bean.getRoomId(), false);
} }
} }
@@ -177,21 +199,9 @@ public class LiveVideoDownloadService {
config.setLogin(StringUtils.hasText(bean.getRecordUid())); config.setLogin(StringUtils.hasText(bean.getRecordUid()));
config.setRoomInfo(roomInfo); config.setRoomInfo(roomInfo);
config.setRootPath(bean.getRecordPath()); config.setRootPath(bean.getRecordPath());
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(),
@@ -220,7 +230,7 @@ public class LiveVideoDownloadService {
String url = urlInfo.getHost() + codec.getBaseUrl() + urlInfo.getExtra(); String url = urlInfo.getHost() + codec.getBaseUrl() + urlInfo.getExtra();
Log.i("下载直播",rawResponse,codec.toString(),urlInfo.toString(),"URL:"+url); Log.i("下载直播", rawResponse, codec.toString(), urlInfo.toString(), "URL:" + url);
if (bean.getRecordLiveModel() == 1) { if (bean.getRecordLiveModel() == 1) {
javaRecord(url, response); javaRecord(url, response);
@@ -285,20 +295,24 @@ 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("-rw_timeout", "60000000")
.withNotSymbolParam("-segment_time", "60")
.withNotSymbolParam("-segment_format", "mpegts")
.withNotSymbolParam("-map", "0")
.withParam("-segment_list", savePath)
.withNotSymbolParam("-c", "copy") .withNotSymbolParam("-c", "copy")
.withNotSymbolParam("-bsf:a", "aac_adtstoasc") .withNotSymbolParam("-bsf:a", "aac_adtstoasc")
// .withNotSymbolParam("-loglevel", "debug") // .withNotSymbolParam("-loglevel", "debug")
.withNotSymbolParam("-y", "") .withNotSymbolParam("-y", "")
//-reconnect 1 -reconnect_at_eof 1 -reconnect_streamed 1 -reconnect_delay_max 2
// .withNotSymbolParam("-progress",new File("cache",config.getRoomId()+".txt").getAbsolutePath()); //输出进度日志,暂时没啥用 // .withNotSymbolParam("-progress",new File("cache",config.getRoomId()+".txt").getAbsolutePath()); //输出进度日志,暂时没啥用
; ;
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); 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() {
@@ -313,6 +327,7 @@ public class LiveVideoDownloadService {
VideoTask.this.onStart(); VideoTask.this.onStart();
Log.i("启动录制:" + playInfo.getRoomId()); Log.i("启动录制:" + playInfo.getRoomId());
task = null; task = null;
cancel(); cancel();
} }
}; };
@@ -330,7 +345,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) {
videoDatabaseBean.setStopTime(new Date());
liveDatabasesService.getLiveDatabase(bean.getRoomId()).addLiveInfo(videoDatabaseBean);
}
stopRecordDanmu();
} }
}); });
@@ -345,9 +371,22 @@ public class LiveVideoDownloadService {
videoDatabaseBean.setPath(savePath); videoDatabaseBean.setPath(savePath);
videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo)); videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo));
videoDatabaseBean.setStartTime(new Date()); videoDatabaseBean.setStartTime(new Date());
database.addLiveInfo(videoDatabaseBean); liveDatabasesService.getLiveDatabase(bean.getRoomId()).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() && !webSocketServer.checkRoom(liveDatabasesService.buildConfig(bean.getRoomId()))) {
webSocketServer.addRoom(liveDatabasesService.buildConfig(bean.getRoomId()), true);
}
}
private void stopRecordDanmu() {
if (bean.isSyncDanmuForLive() && webSocketServer.checkRoom(liveDatabasesService.buildConfig(bean.getRoomId()))) {
webSocketServer.stopRoom(bean.getRoomId(), false);
}
} }
} }
@@ -356,22 +395,18 @@ public class LiveVideoDownloadService {
} }
public VideoFilePath getVideoPath(String roomId) { public VideoFilePath getVideoPath(String roomId) {
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase(); LiveConfigDatabaseBean bean = liveDatabasesService.getConfigDatabase().getConfig(roomId);
LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
configDatabase.close();
return getVideoFilePath(bean); return getVideoFilePath(bean);
} }
private VideoFilePath getVideoFilePath(LiveConfigDatabaseBean configBean) { private VideoFilePath getVideoFilePath(LiveConfigDatabaseBean configBean) {
String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName(); String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName();
File recordDir = new File(recordPath); File recordDir = new File(recordPath);
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId()), recordDir.getAbsolutePath());
VideoFilePath path = createVideoRootFilePath(configBean, recordDir); VideoFilePath path = createVideoRootFilePath(configBean, recordDir);
if (recordDir.exists()) { if (recordDir.exists()) {
List<LiveVideoDatabaseBean> infos = database.getLiveInfos(); List<LiveVideoDatabaseBean> infos = liveDatabasesService.getLiveDatabase(configBean.getRoomId()).getLiveInfos();
path.setChildren(getVideoInfo(infos)); path.setChildren(getVideoInfo(infos));
} }
database.close();
return path; return path;
} }
@@ -409,23 +444,21 @@ public class LiveVideoDownloadService {
public String getVideoPlay(String roomId, String videoId) { public String getVideoPlay(String roomId, String videoId) {
String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class); String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase(); LiveConfigDatabaseBean config = liveDatabasesService.getConfigDatabase().getConfig(roomId);
LiveConfigDatabaseBean config = configDatabase.getConfig(roomId);
String recordPath = config.getRecordPath() + File.separator + config.getAnchorName(); String recordPath = config.getRecordPath() + File.separator + config.getAnchorName();
configDatabase.close();
LiveVideoDatabaseBean videoInfo = null; LiveVideoDatabaseBean videoInfo = null;
BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), new File(recordPath).getAbsolutePath()); for (LiveVideoDatabaseBean info : liveDatabasesService.getLiveDatabase(roomId).getLiveInfos()) {
for (LiveVideoDatabaseBean info : liveDatabase.getLiveInfos()) {
if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) { if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) {
videoInfo = info; videoInfo = info;
break; break;
} }
} }
liveDatabase.close();
if (videoInfo != null) { if (videoInfo != null) {
File videoFile = new File(videoInfo.getPath().replace(".flv", ".mp4")); File videoFile = new File(videoInfo.getPath().replace("-%04d.ts", ".m3u8"));
if (!videoFile.exists()) { if (!videoFile.exists()) {
videoFile = new File(videoInfo.getPath()); videoFile = new File(videoInfo.getPath());
} else {
return videoInfo.getPath().replace(new File("live").getAbsolutePath(), "").replace(File.separator, "/").replace("-%04d.ts", ".m3u8");
} }
FFmpegUtils ffmpeg = FFmpegUtils.segment(videoId, ffmpegPath, videoFile, ConfigTools.load(ConfigTools.CONFIG, "outVideoPath", String.class)); FFmpegUtils ffmpeg = FFmpegUtils.segment(videoId, ffmpegPath, videoFile, ConfigTools.load(ConfigTools.CONFIG, "outVideoPath", String.class));
System.out.println(ffmpeg.getCommandDecode()); System.out.println(ffmpeg.getCommandDecode());

View File

@@ -22,10 +22,12 @@ public class SystemService {
LiveVideoDownloadService videoService; LiveVideoDownloadService videoService;
@Resource @Resource
LiveDanmuService danmuService; LiveDanmuService danmuService;
@Resource
LiveDatabasesService databasesService;
SystemConfigDatabases databases = new SystemConfigDatabases(); SystemConfigDatabases databases = new SystemConfigDatabases();
private ScheduledExecutorService timer; private ScheduledExecutorService timer;
private ScheduledFuture<?> scheduled; private ScheduledFuture<?> scheduled;
BiliLiveConfigDatabase liveConfigDatabase = new BiliLiveConfigDatabase();
public long getLoopTimer() { public long getLoopTimer() {
SystemConfigDatabaseBean config = databases.getConfig(); SystemConfigDatabaseBean config = databases.getConfig();
@@ -35,7 +37,6 @@ public class SystemService {
return config.getTimerLoop(); return config.getTimerLoop();
} }
private final String resetTimer = "00:00:00 - 00:01:00";
public void start() { public void start() {
if (timer == null) { if (timer == null) {
@@ -46,20 +47,23 @@ public class SystemService {
scheduled.cancel(true); scheduled.cancel(true);
} }
scheduled = timer.scheduleAtFixedRate(() -> { scheduled = timer.scheduleAtFixedRate(() -> {
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig(); List<LiveConfigDatabaseBean> list = databasesService.getConfigDatabase().getAllConfig();
Log.i("循环任务:" + list.size()); Log.i("循环任务:" + list.size());
if (DateFormatUtils.getInstance().checkTime(null, resetTimer)) {
videoService.clearUserStopList();
danmuService.clearUserList();
}
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() && !danmuService.check(bean.getRoomId()) && videoService.checkDownload(bean.getRoomId())) {
recordDanmu(bean);
}
// 录制视频
recordVideo(bean); recordVideo(bean);
} }
} catch (Exception e) { } catch (Exception e) {
@@ -77,13 +81,15 @@ public class SystemService {
} }
public void stop() { public void stop() {
scheduled.cancel(true); if(scheduled!=null) {
scheduled.cancel(true);
}
videoService.stopAll(); videoService.stopAll();
} }
// 录制弹幕 // 录制弹幕
private void recordDanmu(LiveConfigDatabaseBean bean) { private void recordDanmu(LiveConfigDatabaseBean bean) {
danmuService.start(bean.getRoomId().toString(), false); danmuService.start(bean, false);
} }
// 录制视频 // 录制视频

View File

@@ -7,6 +7,7 @@ import com.alibaba.fastjson2.JSONObject;
import com.alibaba.fastjson2.annotation.JSONField; import com.alibaba.fastjson2.annotation.JSONField;
import com.alibaba.fastjson2.util.DateUtils; import com.alibaba.fastjson2.util.DateUtils;
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean; import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
import com.yutou.bilibili.Tools.DateFormatUtils;
import com.yutou.common.inter.ISqlDatabaseBean; import com.yutou.common.inter.ISqlDatabaseBean;
import com.yutou.common.utils.Log; import com.yutou.common.utils.Log;
import lombok.Data; import lombok.Data;
@@ -87,7 +88,7 @@ public abstract class SQLiteManager {
} }
} }
public void close() { public synchronized void close() {
try { try {
conn.close(); conn.close();
} catch (SQLException throwables) { } catch (SQLException throwables) {
@@ -103,134 +104,123 @@ public abstract class SQLiteManager {
} }
} }
protected <T extends AbsDatabasesBean> void add(T t) { protected synchronized <T extends AbsDatabasesBean> void add(T t) {
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
PreparedStatement statement = null; try (PreparedStatement statement = getConnection().prepareStatement(buildInsertSql(t))) {
try {
StringBuilder value = new StringBuilder();
sb.append("INSERT INTO `").append(t.getTableName()).append("` ");
sb.append("(");
value.append("(");
JSONObject json = t.toJson(); JSONObject json = t.toJson();
Set<String> keySet = json.keySet(); Set<String> keySet = json.keySet();
for (String key : keySet) {
if ("id".equals(key)) {
continue;
}
sb.append("`").append(key).append("`,");
//value.append("'").append(json.get(key)).append("',");
value.append("?").append(",");
}
sb.deleteCharAt(sb.length() - 1);
value.deleteCharAt(value.length() - 1);
value.append(")");
sb.append(") VALUES ");
sb.append(value);
statement = getConnection().prepareStatement(sb.toString());
int i = 1; int i = 1;
for (String key : keySet) { for (String key : keySet) {
if ("id".equals(key)) { if ("id".equals(key)) {
continue; continue;
} }
Object value = json.get(key);
if ("sql_time".equals(key)) { if ("sql_time".equals(key)) {
statement.setLong(i++, json.getDate(key).getTime()); statement.setLong(i++, DateFormatUtils.getInstance().parse(value.toString(),DateFormatUtils.DEFAULT_PATTERN).getTime());
continue; continue;
} }
if (json.get(key) instanceof String) { if (value instanceof String) {
statement.setString(i++, json.get(key).toString()); statement.setString(i++, (String) value);
} else if (json.get(key) instanceof Integer) { } else if (value instanceof Integer) {
statement.setInt(i++, json.getInteger(key)); statement.setInt(i++, (Integer) value);
} else if (json.get(key) instanceof Long) { } else if (value instanceof Long) {
statement.setLong(i++, json.getLong(key)); statement.setLong(i++, (Long) value);
} else if (json.get(key) instanceof Boolean) { } else if (value instanceof Boolean) {
statement.setBoolean(i++, json.getBoolean(key)); statement.setBoolean(i++, (Boolean) value);
} else if (json.get(key) instanceof Date) { } else if (value instanceof Date) {
statement.setLong(i++, json.getDate(key).getTime()); statement.setLong(i++, (Long) value);
} else { } else {
statement.setObject(i++, json.get(key)); statement.setObject(i++, value);
} }
} }
statement.execute(); statement.execute();
} catch (SQLException e) { } catch (SQLException e) {
Log.e(e, sb); Log.e(e, sb.toString());
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
try {
if (statement != null) {
statement.closeOnCompletion();
}
} catch (SQLException e) {
Log.e(e);
}
} }
} }
protected <T extends AbsDatabasesBean> void update(T t) { private String buildInsertSql(AbsDatabasesBean bean) {
PreparedStatement statement = null; StringBuilder sb = new StringBuilder();
try { StringBuilder value = new StringBuilder();
StringBuilder sb = new StringBuilder(); sb.append("INSERT INTO `").append(bean.getTableName()).append("` ");
StringBuilder setPart = new StringBuilder(); sb.append("(");
value.append("(");
JSONObject json = bean.toJson();
Set<String> keySet = json.keySet();
for (String key : keySet) {
if ("id".equals(key)) {
continue;
}
sb.append("`").append(key).append("`,");
value.append("?").append(",");
}
sb.deleteCharAt(sb.length() - 1); // 删除最后一个逗号
value.deleteCharAt(value.length() - 1); // 删除最后一个逗号
value.append(")");
sb.append(") VALUES ").append(value);
return sb.toString();
}
protected synchronized <T extends AbsDatabasesBean> void update(T t) {
try (PreparedStatement statement = getConnection().prepareStatement(buildUpdateSql(t))) {
JSONObject json = t.toJson(); JSONObject json = t.toJson();
Set<String> keySet = json.keySet(); Set<String> keySet = json.keySet();
for (String key : keySet) {
if ("id".equals(key) || "sql_time".equals(key)) {
continue;
}
setPart.append("`").append(key).append("` = ?, ");
}
setPart.deleteCharAt(setPart.length() - 2);
sb.append("UPDATE `").append(t.getTableName()).append("` ");
sb.append("SET ").append(setPart);
sb.append(" WHERE `sql_time` = ?");
statement = getConnection().prepareStatement(sb.toString());
int i = 1; int i = 1;
for (String key : keySet) { for (String key : keySet) {
if ("id".equals(key) || "sql_time".equals(key)) { if ("id".equals(key) || "sql_time".equals(key)) {
continue; continue;
} }
Object value = json.get(key);
if (json.get(key) instanceof String) { if (value instanceof String) {
statement.setString(i++, json.getString(key)); statement.setString(i++, (String) value);
} else if (json.get(key) instanceof Integer) { } else if (value instanceof Integer) {
statement.setInt(i++, json.getInteger(key)); statement.setInt(i++, (Integer) value);
} else if (json.get(key) instanceof Long) { } else if (value instanceof Long) {
statement.setLong(i++, json.getLong(key)); statement.setLong(i++, (Long) value);
} else if (json.get(key) instanceof Boolean) { } else if (value instanceof Boolean) {
statement.setBoolean(i++, json.getBoolean(key)); statement.setBoolean(i++, (Boolean) value);
} else if (json.get(key) instanceof Date) { } else if (value instanceof Date) {
statement.setLong(i++, json.getDate(key).getTime()); statement.setTimestamp(i++, new Timestamp(((Date) value).getTime()));
} else { } else {
statement.setObject(i++, json.get(key)); statement.setObject(i++, value);
} }
} }
// 设置 sql_time 的值 // 设置 sql_time 的值
long id = t.getSql_time().getTime(); long sqlTime = t.getSql_time().getTime();
statement.setLong(i, id); statement.setLong(i, sqlTime);
statement.executeUpdate(); statement.executeUpdate();
} catch (SQLException e) { } catch (SQLException e) {
Log.e(e); Log.e(e);
throw new RuntimeException(e); throw new RuntimeException(e);
} finally {
try {
if (statement != null) {
statement.closeOnCompletion();
}
} catch (SQLException e) {
Log.e(e);
}
} }
} }
private String buildUpdateSql(AbsDatabasesBean bean) {
StringBuilder sb = new StringBuilder();
StringBuilder setPart = new StringBuilder();
JSONObject json = bean.toJson();
Set<String> keySet = json.keySet();
for (String key : keySet) {
if ("id".equals(key) || "sql_time".equals(key)) {
continue;
}
setPart.append("`").append(key).append("` = ?, ");
}
setPart.deleteCharAt(setPart.length() - 2); // 删除最后一个逗号和空格
sb.append("UPDATE `").append(bean.getTableName()).append("` ");
sb.append("SET ").append(setPart);
sb.append(" WHERE `sql_time` = ?");
return sb.toString();
}
protected JSONArray getJSONArray(String table) { protected JSONArray getJSONArray(String table) {
return getJSONArray(table, null); return getJSONArray(table, null);
} }
@@ -377,14 +367,13 @@ public abstract class SQLiteManager {
return json; return json;
} }
protected <T extends AbsDatabasesBean> boolean delete(T t) { protected synchronized <T extends AbsDatabasesBean> boolean delete(T t) {
Statement statement = null; Statement statement = null;
try { try {
String id = DateUtils.format(t.getSql_time(), "yyyy-MM-dd HH:mm:ss.SSS");
statement = getConnection().createStatement(); statement = getConnection().createStatement();
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
sb.append("DELETE FROM `").append(t.getTableName()).append("` "); sb.append("DELETE FROM `").append(t.getTableName()).append("` ");
sb.append(" WHERE `sql_time` = ").append("'").append(id).append("'"); sb.append(" WHERE `sql_time` = ").append("'").append(t.getSql_time().getTime()).append("'");
int ret = statement.executeUpdate(sb.toString()); int ret = statement.executeUpdate(sb.toString());
return ret != 0; return ret != 0;
} catch (Exception e) { } catch (Exception e) {

View File

@@ -8,13 +8,14 @@ import okio.Buffer;
import java.io.IOException; import java.io.IOException;
import java.nio.charset.Charset; import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;
import java.util.logging.Logger; import java.util.logging.Logger;
public class HttpLoggingInterceptor implements Interceptor { public class HttpLoggingInterceptor implements Interceptor {
private static final String TAG = "HttpLogging"; private static final String TAG = "HttpLogging";
private static final Charset UTF8 = Charset.forName("UTF-8"); private static final Charset UTF8 = StandardCharsets.UTF_8;
private volatile Level printLevel = Level.BODY; private volatile Level printLevel = Level.BODY;
private java.util.logging.Level colorLevel; private java.util.logging.Level colorLevel;
@@ -51,7 +52,7 @@ public class HttpLoggingInterceptor implements Interceptor {
private void log(String message) { private void log(String message) {
//logger.log(colorLevel, message); //logger.log(colorLevel, message);
if (prLog) { if (prLog) {
Log.i(TAG, message); Log.getDynamicLogger(TAG).info(message);
} }
//Log.e(TAG,message); //Log.e(TAG,message);
} }

View File

@@ -0,0 +1,123 @@
package com.yutou.common.utils;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import com.yutou.bilibili.Tools.DateFormatUtils;
import org.apache.logging.log4j.Level;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.core.Appender;
import org.apache.logging.log4j.core.Layout;
import org.apache.logging.log4j.core.LoggerContext;
import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.core.appender.rolling.*;
import org.apache.logging.log4j.core.config.Configuration;
import org.apache.logging.log4j.core.appender.RollingFileAppender;
import org.apache.logging.log4j.core.layout.PatternLayout;
import java.util.Date;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
public class DynamicLogFile {
// 创建一个缓存用于存储Logger对象最大容量为1000过期时间为10分钟
static Cache<String, Logger> cache = CacheBuilder.newBuilder()
.maximumSize(1000)
.expireAfterAccess(10, TimeUnit.MINUTES)
.removalListener(it -> {
if (it.wasEvicted()) {
if (it.getKey() != null) {
String loggerName = (String) it.getKey();
remove(loggerName, true);
}
}
})
.build();
// 根据loggerName获取Logger对象如果缓存中不存在则创建一个新的Logger对象并放入缓存
public static Logger getLogger(String loggerName) {
try {
return cache.get(loggerName, () -> {
configureLogger(loggerName);
return LogManager.getLogger(loggerName);
});
} catch (ExecutionException e) {
throw new RuntimeException(e);
}
}
// 配置Logger对象
private static void configureLogger(String loggerName) {
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
// 创建日志格式
Layout<String> layout = PatternLayout.newBuilder()
.withPattern("%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5p [%thread] (%F:%L) : %m%n")
.build();
// 创建时间触发策略
TimeBasedTriggeringPolicy timePolicy = TimeBasedTriggeringPolicy.newBuilder()
.build();
// 创建文件大小触发策略
SizeBasedTriggeringPolicy sizePolicy = SizeBasedTriggeringPolicy.createPolicy("100 MB");
// 创建组合触发策略
CompositeTriggeringPolicy triggeringPolicy = CompositeTriggeringPolicy.createPolicy(timePolicy, sizePolicy);
// 创建滚动文件Appender
RollingFileAppender appender = RollingFileAppender.newBuilder()
.setName(loggerName)
.withFileName("logs" + "/" + DateFormatUtils.getInstance().format(new Date(), "yyyy-MM-dd") + "/" + loggerName + ".log")
.withFilePattern("logs" + "/" + "%d{yyyy-MM-dd}" + "/" + loggerName + "-%i.log.gz")
.setLayout(layout)
.setImmediateFlush(true)
.withAppend(true)
.setIgnoreExceptions(false)
.withPolicy(triggeringPolicy)
.build();
appender.start();
config.addAppender(appender);
// 获取Logger对象
org.apache.logging.log4j.core.Logger coreLogger = context.getLogger(loggerName);
if (coreLogger == null) {
throw new IllegalStateException("Logger with name " + loggerName + " does not exist.");
}
// 将Appender添加到Logger对象中
coreLogger.addAppender(appender);
coreLogger.setLevel(Level.ALL);
coreLogger.setAdditive(false);
// 更新Logger对象
context.updateLoggers();
}
// 移除Logger对象
public static void remove(String loggerName) {
remove(loggerName, false);
}
// 私有方法移除Logger对象isAuto参数用于判断是否是自动移除
private static void remove(String loggerName, boolean isAuto) {
if (!isAuto) {
cache.invalidate(loggerName);
}
LoggerContext context = (LoggerContext) LogManager.getContext(false);
Configuration config = context.getConfiguration();
org.apache.logging.log4j.core.Logger coreLogger = context.getLogger(loggerName);
Appender appender = config.getAppender(loggerName);
if (appender != null) {
appender.stop();
coreLogger.removeAppender(appender);
}
config.getAppenders().remove(loggerName);
context.updateLoggers();
}
}

View File

@@ -182,7 +182,6 @@ public class FFmpegUtils extends AbsVideoRecord {
process.exitValue(); process.exitValue();
return (long) (Double.parseDouble(data) * 1000); return (long) (Double.parseDouble(data) * 1000);
} catch (Exception e) { } catch (Exception e) {
Log.e(e);
return 0; return 0;
} }
} }

View File

@@ -1,10 +1,8 @@
package com.yutou.common.utils; package com.yutou.common.utils;
import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.LogManager;
import org.apache.logging.log4j.Marker; import org.apache.logging.log4j.Logger;
import org.apache.logging.log4j.MarkerManager;
import org.apache.logging.log4j.util.StackLocatorUtil; import org.apache.logging.log4j.util.StackLocatorUtil;
@@ -14,6 +12,14 @@ public class Log {
System.out.println(); System.out.println();
} }
public static Logger getDynamicLogger(String loggerName) {
return DynamicLogFile.getLogger(loggerName);
}
public static void removeDynamicLogger(String loggerName) {
DynamicLogFile.remove(loggerName);
}
public static void i(Object... log) { public static void i(Object... log) {
if (!((boolean) ConfigTools.load(ConfigTools.CONFIG, "logcat"))) { if (!((boolean) ConfigTools.load(ConfigTools.CONFIG, "logcat"))) {
return; return;

View File

@@ -27,7 +27,7 @@ public class RedisTools {
//Properties properties = PropertyUtil.loadProperties("jedis.properties"); //Properties properties = PropertyUtil.loadProperties("jedis.properties");
//host = properties.getProperty("redis.host"); //host = properties.getProperty("redis.host");
//port = Integer.valueOf(properties.getProperty("redis.port")); //port = Integer.valueOf(properties.getProperty("redis.port"));
host = "172.21.35.118"; host = "192.168.31.148";
port = 6379; port = 6379;
} }
@@ -41,7 +41,6 @@ public class RedisTools {
String ret = jedis.set(key, value); String ret = jedis.set(key, value);
jedis.close(); jedis.close();
} catch (Exception e) { } catch (Exception e) {
// TODO: handle exception
Log.e(e); Log.e(e);
return false; return false;
} }
@@ -71,7 +70,6 @@ public class RedisTools {
} }
jedis.close(); jedis.close();
} catch (Exception e) { } catch (Exception e) {
// TODO: handle exception
Log.e(e); Log.e(e);
return false; return false;
} }

View File

@@ -1,3 +1,9 @@
server.port=8080 server.port=8880
logging.config=classpath:log4j2.xml
logging.file.path=./logs logging.file.path=./logs
logging.level.com.log.controller = trace logging.level.com.log.controller = trace
# 启用Hibernate SQL日志
spring.jpa.show-sql=true
spring.jpa.properties.hibernate.format_sql=true
logging.level.org.hibernate.SQL=DEBUG
logging.level.org.hibernate.type.descriptor.sql.BasicBinder=TRACE

View File

@@ -0,0 +1,37 @@
<?xml version="1.0" encoding="UTF-8"?>
<Configuration status="WARN">
<Appenders>
<!-- 控制台输出 -->
<Console name="ConsoleAppender" target="SYSTEM_OUT">
<PatternLayout pattern="%d{yyyy-MM-dd HH:mm:ss.SSS} %-5p [%thread] (%F:%L) : %m%n%throwable"/>
</Console>
<!-- 动态路由日志文件 -->
<Routing name="RoutingAppender">
<Routes pattern="$${date:yyyy-MM-dd}">
<Route>
<RollingFile name="Rolling-${date:yyyy-MM-dd}" fileName="logs/${date:yyyy-MM-dd}/system.log"
filePattern="logs/%d{yyyy-MM-dd}/system-%i.log.gz">
<PatternLayout>
<pattern>%d{yyyy-MM-dd'T'HH:mm:ss.SSSXXX} %-5p [%thread] (%F:%L) : %m%n%throwable</pattern>
</PatternLayout>
<Policies>
<TimeBasedTriggeringPolicy />
<SizeBasedTriggeringPolicy size="1024 MB"/>
</Policies>
</RollingFile>
</Route>
</Routes>
</Routing>
</Appenders>
<Loggers>
<!-- 根日志记录器 -->
<Root level="info">
<AppenderRef ref="ConsoleAppender"/>
<AppenderRef ref="RoutingAppender"/>
</Root>
</Loggers>
</Configuration>