更新弹幕、直播的下载,以及弹幕的转换还有部分接口
This commit is contained in:
@@ -24,7 +24,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "set", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> setConfig(String url, LiveConfigDatabaseBean bean) {
|
||||
public JSONObject setConfig(String url, LiveConfigDatabaseBean bean) {
|
||||
LiveConfigDatabaseBean config = configService.addConfig(url, bean);
|
||||
if (config != null) {
|
||||
return ResultData.success(config.toJson());
|
||||
@@ -34,7 +34,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "update", method = RequestMethod.POST)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> updateConfig(String roomId, LiveConfigDatabaseBean bean) {
|
||||
public JSONObject updateConfig(String roomId, LiveConfigDatabaseBean bean) {
|
||||
LiveConfigDatabaseBean config = configService.updateConfig(new BigInteger(roomId), bean);
|
||||
if (config != null) {
|
||||
return ResultData.success(config.toJson());
|
||||
@@ -44,7 +44,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "get", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> getConfig(String roomId) {
|
||||
public JSONObject getConfig(String roomId) {
|
||||
if ("0".equals(roomId) || !StringUtils.hasText(roomId)) {
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
@@ -57,7 +57,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "all", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONArray> getAllConfig() {
|
||||
public JSONObject getAllConfig() {
|
||||
List<LiveConfigDatabaseBean> config = configService.getAllConfig();
|
||||
if (config != null) {
|
||||
return ResultData.success(JSONArray.parseArray(JSONArray.toJSONString(config)));
|
||||
@@ -67,7 +67,7 @@ public class LiveConfigController {
|
||||
|
||||
@RequestMapping(value = "delete", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public ResultData<JSONObject> deleteConfig(BigInteger roomId) {
|
||||
public JSONObject deleteConfig(BigInteger roomId) {
|
||||
if (roomId.equals(BigInteger.ZERO)) {
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
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.ReturnCode;
|
||||
import com.yutou.bilibili.services.LiveDanmuService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
@Controller
|
||||
public class LiveDanmuController {
|
||||
@Resource
|
||||
LiveDanmuService service;
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/list")
|
||||
public JSONObject getLiveDanmuList() {
|
||||
return ResultData.success(service.getLiveRoomList());
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/stop")
|
||||
public JSONObject stopLiveDanmu(String roomId) {
|
||||
service.stop(roomId);
|
||||
return ResultData.success(ReturnCode.RC100);
|
||||
}
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/start")
|
||||
public JSONObject startLiveDanmu(String roomId) {
|
||||
service.start(roomId);
|
||||
return ResultData.success(ReturnCode.RC100);
|
||||
}
|
||||
@ResponseBody
|
||||
@RequestMapping("/live/danmu/file/list")
|
||||
public JSONObject getDanmuList(String roomId) {
|
||||
return ResultData.success(service.getDanmuFileList(roomId));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,49 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.ReturnCode;
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
public class LiveVideoController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
|
||||
@RequestMapping("/live/video/list")
|
||||
@ResponseBody
|
||||
public JSONObject getLiveVideoList() {
|
||||
return ResultData.success(videoService.getDownloadTasks());
|
||||
}
|
||||
|
||||
@RequestMapping("/live/video/stop")
|
||||
@ResponseBody
|
||||
public JSONObject stopDownload(String roomId) {
|
||||
videoService.stop(roomId, true);
|
||||
return ResultData.success(true);
|
||||
}
|
||||
|
||||
@RequestMapping("/live/video/start")
|
||||
@ResponseBody
|
||||
public JSONObject startDownload(String roomId) {
|
||||
BiliLiveConfigDatabase liveConfigDatabase = new BiliLiveConfigDatabase();
|
||||
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
if (bean.getRoomId().toString().equals(roomId)) {
|
||||
videoService.start(bean, true);
|
||||
return ResultData.success(true);
|
||||
}
|
||||
}
|
||||
return ResultData.fail(ReturnCode.RC999);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,46 @@
|
||||
package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.bilibili.Tools.FileServerUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.VideoFilePath;
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.core.io.FileSystemResource;
|
||||
import org.springframework.http.ResponseEntity;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.bind.annotation.PathVariable;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
import org.springframework.web.bind.annotation.ResponseBody;
|
||||
|
||||
import java.io.File;
|
||||
import java.util.List;
|
||||
|
||||
@Controller
|
||||
public class VideoFileController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/file/list")
|
||||
public JSONObject getFileList(String roomId) {
|
||||
List<VideoFilePath> list;
|
||||
if (StringUtils.hasText(roomId)) {
|
||||
list = videoService.getVideoPath(roomId);
|
||||
} else {
|
||||
list = videoService.getAllVideoPath();
|
||||
}
|
||||
return ResultData.success(list);
|
||||
}
|
||||
@RequestMapping("/file/{base64}")
|
||||
@ResponseBody
|
||||
public ResponseEntity<FileSystemResource> getFile(@PathVariable String base64) {
|
||||
File file = FileServerUtils.toFile(base64);
|
||||
System.out.println(file.getAbsolutePath());
|
||||
return Tools.getFile(file);
|
||||
}
|
||||
|
||||
}
|
||||
@@ -1,14 +1,19 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import com.yutou.bilibili.services.SystemService;
|
||||
import com.yutou.common.utils.Log;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.context.ApplicationListener;
|
||||
import org.springframework.context.event.ContextClosedEvent;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Component
|
||||
public class ApplicationClose implements ApplicationListener<ContextClosedEvent> {
|
||||
@Resource
|
||||
SystemService systemConfigService;
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
|
||||
Log.i("服务结束");
|
||||
systemConfigService.stop();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,7 +17,7 @@ public class AssTools {
|
||||
private final Date startTime;
|
||||
private int y = 0;
|
||||
private List<String> filters = new ArrayList<>();
|
||||
private String alpha="80";
|
||||
private String alpha="100";
|
||||
|
||||
/**
|
||||
* 弹幕转换ass
|
||||
@@ -140,7 +140,7 @@ public class AssTools {
|
||||
y,
|
||||
x2,
|
||||
y,
|
||||
danmuData.getFontColorHex(),
|
||||
danmuData.getFontColor(),
|
||||
alpha,
|
||||
danmuData.getDanmu()
|
||||
);
|
||||
|
||||
22
src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java
Normal file
22
src/main/java/com/yutou/bilibili/Tools/DateFormatUtils.java
Normal file
@@ -0,0 +1,22 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
|
||||
public class DateFormatUtils {
|
||||
public static String format(Date date,String format){
|
||||
return new SimpleDateFormat(format).format(date);
|
||||
}
|
||||
public static String format(long time,String format){
|
||||
return new SimpleDateFormat(format).format(new Date(time));
|
||||
}
|
||||
public static String format(long time){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date(time));
|
||||
}
|
||||
public static String format(Date date){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(date);
|
||||
}
|
||||
public static String format(){
|
||||
return new SimpleDateFormat("yyyy-MM-dd HH:mm:ss.SSS").format(new Date());
|
||||
}
|
||||
}
|
||||
18
src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java
Normal file
18
src/main/java/com/yutou/bilibili/Tools/FileServerUtils.java
Normal file
@@ -0,0 +1,18 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import com.yutou.common.utils.Base64Tools;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
public class FileServerUtils {
|
||||
public static String toUrl(String file) {
|
||||
return "/file/" + URLEncoder.encode(Base64Tools.encode(file), StandardCharsets.UTF_8);
|
||||
}
|
||||
|
||||
public static File toFile(String base64) {
|
||||
return new File(URLDecoder.decode(Base64Tools.decode(base64), StandardCharsets.UTF_8));
|
||||
}
|
||||
}
|
||||
@@ -13,11 +13,11 @@ import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
import java.net.URLDecoder;
|
||||
import java.net.URLEncoder;
|
||||
import java.nio.file.*;
|
||||
import java.nio.file.attribute.BasicFileAttributes;
|
||||
import java.security.MessageDigest;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Base64;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.*;
|
||||
|
||||
public class Tools {
|
||||
|
||||
@@ -203,4 +203,24 @@ public class Tools {
|
||||
public static String getToDayTime() {
|
||||
return new SimpleDateFormat("yyyy-MM-dd").format(new Date());
|
||||
}
|
||||
|
||||
//扫描文件夹
|
||||
public static List<File> scanFile(File file){
|
||||
List<File> list = new ArrayList<>();
|
||||
FileVisitor<Path> visitor = new SimpleFileVisitor<>() {
|
||||
@Override
|
||||
public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException {
|
||||
if ("live.db".equals(file.toFile().getName())) {
|
||||
list.add(file.toFile());
|
||||
}
|
||||
return super.visitFile(file, attrs);
|
||||
}
|
||||
};
|
||||
try {
|
||||
Files.walkFileTree(Paths.get(file.getAbsolutePath()), EnumSet.of(FileVisitOption.FOLLOW_LINKS), Integer.MAX_VALUE, visitor);
|
||||
return list;
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ public class DanmuData {
|
||||
private int id;
|
||||
private int model;//1~3 滚动弹幕 4 底端弹幕 5 顶端弹幕 6 逆向弹幕 7 精准定位 8 高级弹幕
|
||||
private int fontSize;
|
||||
private int fontColor;
|
||||
private String fontColor;
|
||||
private long time;
|
||||
private String uCode;
|
||||
private String danmu;
|
||||
@@ -20,7 +20,4 @@ public class DanmuData {
|
||||
return new Date(time);
|
||||
}
|
||||
|
||||
public String getFontColorHex() {
|
||||
return Integer.toHexString(fontColor);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package com.yutou.bilibili.datas;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import lombok.Data;
|
||||
|
||||
@Data
|
||||
@@ -9,33 +10,33 @@ public class ResultData<T> {
|
||||
private T data;
|
||||
private long timestamp ;
|
||||
|
||||
public ResultData (){
|
||||
public ResultData() {
|
||||
this.timestamp = System.currentTimeMillis();
|
||||
}
|
||||
|
||||
|
||||
public static <T> ResultData<T> success(T data) {
|
||||
public static <T> JSONObject success(T data) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(ReturnCode.RC100.getCode());
|
||||
resultData.setMessage(ReturnCode.RC100.getMessage());
|
||||
resultData.setData(data);
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
|
||||
public static <T> ResultData<T> fail(int code, String message) {
|
||||
public static <T> JSONObject fail(int code, String message) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(code);
|
||||
resultData.setMessage(message);
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
public static <T> ResultData<T> success(ReturnCode code) {
|
||||
public static JSONObject success(ReturnCode code) {
|
||||
return fail(code);
|
||||
}
|
||||
public static <T> ResultData<T> fail(ReturnCode code) {
|
||||
public static <T> JSONObject fail(ReturnCode code) {
|
||||
ResultData<T> resultData = new ResultData<>();
|
||||
resultData.setStatus(code.getCode());
|
||||
resultData.setMessage(code.getMessage());
|
||||
return resultData;
|
||||
return JSONObject.parseObject(JSONObject.toJSONString(resultData));
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -9,7 +9,7 @@ import lombok.EqualsAndHashCode;
|
||||
@Data
|
||||
public class SystemConfigDatabaseBean extends AbsDatabasesBean {
|
||||
@JSONField(name = "timer_loop")
|
||||
private long timerLoop = 5000;
|
||||
private long timerLoop = 30000;
|
||||
|
||||
|
||||
public SystemConfigDatabaseBean() {
|
||||
|
||||
15
src/main/java/com/yutou/bilibili/datas/VideoFilePath.java
Normal file
15
src/main/java/com/yutou/bilibili/datas/VideoFilePath.java
Normal file
@@ -0,0 +1,15 @@
|
||||
package com.yutou.bilibili.datas;
|
||||
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
@Data
|
||||
public class VideoFilePath {
|
||||
private String name;
|
||||
private String roomId;
|
||||
private String path;
|
||||
private String cover;
|
||||
private boolean isParent;
|
||||
private List<VideoFilePath> children;
|
||||
}
|
||||
@@ -3,7 +3,8 @@ package com.yutou.bilibili.interfaces;
|
||||
import java.io.File;
|
||||
|
||||
public abstract class DownloadInterface {
|
||||
public void onDownloading(double soFarBytes, double totalBytes){};
|
||||
public void onDownload(File file){};
|
||||
public void onError(Exception e){};
|
||||
public void onDownloadStart(){}
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes){return true;}
|
||||
public void onDownload(File file){}
|
||||
public void onError(Exception e){}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,83 @@
|
||||
package com.yutou.bilibili.services;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveDanmuDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveDatabase;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.Tools.AssTools;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.File;
|
||||
import java.math.BigInteger;
|
||||
import java.util.List;
|
||||
|
||||
@Service
|
||||
public class LiveDanmuService {
|
||||
|
||||
public void start(String roomId) {
|
||||
WebSocketManager.getInstance().addRoom(LiveRoomConfig.buildConfig(roomId), true);
|
||||
}
|
||||
|
||||
public void stop(String roomId) {
|
||||
WebSocketManager.getInstance().stopRoom(roomId, true);
|
||||
}
|
||||
|
||||
public JSONArray getLiveRoomList() {
|
||||
return WebSocketManager.getInstance().getLiveRoomList();
|
||||
}
|
||||
|
||||
|
||||
private BiliLiveDatabase getDatabase(String roomId, File file) {
|
||||
return new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), file.getParent() + File.separator + "live.db");
|
||||
}
|
||||
public List<File> getDanmuFileList(String roomId) {
|
||||
BiliLiveConfigDatabase configDatabase=new BiliLiveConfigDatabase();
|
||||
LiveConfigDatabaseBean bean = configDatabase.getConfig(new BigInteger(roomId));
|
||||
configDatabase.close();
|
||||
return Tools.scanFile(new File(bean.getRecordPath() + File.separator + bean.getAnchorName()));
|
||||
}
|
||||
|
||||
public void saveDanmuXML(LiveVideoDatabaseBean videoDatabaseBean, BiliLiveDatabase database) {
|
||||
File videoFile = new File(videoDatabaseBean.getPath());
|
||||
long videoTime = FFmpegUtils.getVideoTime(videoFile) + videoDatabaseBean.getStartTime().getTime();
|
||||
System.out.println("开始时间:" + videoDatabaseBean.getStartTime().getTime());
|
||||
System.out.println("结束时间:" + videoTime);
|
||||
String startTime = DateFormatUtils.format(videoDatabaseBean.getStartTime());
|
||||
String endTime = DateFormatUtils.format(videoTime);
|
||||
List<LiveDanmuDatabaseBean> danmus = database.getOfTime(startTime, endTime, LiveDanmuDatabaseBean.class);
|
||||
System.out.println("弹幕数量:" + danmus.size());
|
||||
AssTools assTools = new AssTools(videoFile.getName().replace(".flv", ""), videoDatabaseBean.getStartTime());
|
||||
for (LiveDanmuDatabaseBean dm : danmus) {
|
||||
assTools.addDanmu(dm.createDanmuData());
|
||||
}
|
||||
assTools.saveDanmu(videoFile.getAbsolutePath().replace(".flv", ".ass"));
|
||||
}
|
||||
|
||||
public String toTimeString(long videoTime) {
|
||||
long seconds = videoTime / 1000;
|
||||
long hours = seconds / 3600;
|
||||
long remainingSecondsAfterHours = seconds % 3600;
|
||||
long minutes = remainingSecondsAfterHours / 60;
|
||||
long finalRemainingSeconds = remainingSecondsAfterHours % 60;
|
||||
// long finalRemainingMilliseconds = videoTime % 1000;
|
||||
return String.format("%d小时%d分钟%d秒", hours, minutes, finalRemainingSeconds);
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LiveDanmuService service = new LiveDanmuService();
|
||||
List<File> files = service.getDanmuFileList("22047448");
|
||||
for (File file : files) {
|
||||
System.out.println(file);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
377
src/main/java/com/yutou/bilibili/services/LiveVideoService.java
Normal file
377
src/main/java/com/yutou/bilibili/services/LiveVideoService.java
Normal file
@@ -0,0 +1,377 @@
|
||||
package com.yutou.bilibili.services;
|
||||
|
||||
import com.alibaba.fastjson2.JSONArray;
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.alibaba.fastjson2.util.DateUtils;
|
||||
import com.yutou.biliapi.api.LiveApi;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomConfig;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomInfo;
|
||||
import com.yutou.biliapi.bean.live.LiveRoomPlayInfo;
|
||||
import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.bean.live.database.LiveVideoDatabaseBean;
|
||||
import com.yutou.biliapi.bean.login.LoginCookieDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliBiliLoginDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.databases.BiliLiveDatabase;
|
||||
import com.yutou.biliapi.enums.LiveProtocol;
|
||||
import com.yutou.biliapi.enums.LiveVideoCodec;
|
||||
import com.yutou.biliapi.enums.LiveVideoDefinition;
|
||||
import com.yutou.biliapi.enums.LiveVideoFormat;
|
||||
import com.yutou.biliapi.net.BiliLiveNetApiManager;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.Tools.FileServerUtils;
|
||||
import com.yutou.bilibili.Tools.Tools;
|
||||
import com.yutou.bilibili.datas.VideoFilePath;
|
||||
import com.yutou.bilibili.interfaces.DownloadInterface;
|
||||
import com.yutou.common.okhttp.HttpCallback;
|
||||
import com.yutou.common.okhttp.HttpDownloadUtils;
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import okhttp3.Headers;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileFilter;
|
||||
import java.math.BigInteger;
|
||||
import java.util.*;
|
||||
import java.util.concurrent.ArrayBlockingQueue;
|
||||
import java.util.concurrent.ThreadPoolExecutor;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import static com.alibaba.fastjson2.util.DateUtils.DateTimeFormatPattern.DATE_FORMAT_10_DASH;
|
||||
|
||||
@Service
|
||||
public class LiveVideoService {
|
||||
ThreadPoolExecutor executor;
|
||||
private final Map<LiveConfigDatabaseBean, VideoTask> liveVideoMap = new HashMap<>();
|
||||
private final List<String> userStopList = new ArrayList<>();//手动停止列表
|
||||
|
||||
|
||||
public LiveVideoService() {
|
||||
System.out.println("初始化下载服务");
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
|
||||
public void start(LiveConfigDatabaseBean bean, boolean isUser) {
|
||||
if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.remove(bean.getRoomId().toString());
|
||||
}
|
||||
if (liveVideoMap.containsKey(bean)) {
|
||||
return;
|
||||
}
|
||||
System.out.println("添加下载任务:" + liveVideoMap.keySet().size());
|
||||
liveVideoMap.put(bean, null);
|
||||
BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid()).getRoomInfo(bean.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
|
||||
if (response.getLiveStatus() == 1) {
|
||||
VideoTask task = new VideoTask(bean, response);
|
||||
executor.execute(task);
|
||||
liveVideoMap.put(bean, task);
|
||||
} else {
|
||||
liveVideoMap.remove(bean);
|
||||
System.out.println("移除下载");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
liveVideoMap.remove(bean);
|
||||
System.out.println("移除下载");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void stop(String roomId, boolean isUser) {
|
||||
if (isUser) {
|
||||
userStopList.add(roomId);
|
||||
}
|
||||
for (Map.Entry<LiveConfigDatabaseBean, VideoTask> entry : liveVideoMap.entrySet()) {
|
||||
if (entry.getKey().getRoomId().toString().equals(roomId)) {
|
||||
entry.getValue().isDownload = false;
|
||||
liveVideoMap.remove(entry.getKey());
|
||||
System.out.println("移除下载");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public boolean check(String roomId) {
|
||||
for (LiveConfigDatabaseBean bean : liveVideoMap.keySet()) {
|
||||
if (bean.getRoomId().toString().equals(roomId)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public JSONArray getDownloadTasks() {
|
||||
JSONArray array = new JSONArray();
|
||||
array.addAll(liveVideoMap.keySet());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
for (VideoTask task : liveVideoMap.values()) {
|
||||
task.isDownload = false;
|
||||
}
|
||||
liveVideoMap.clear();
|
||||
}
|
||||
|
||||
private class VideoTask implements Runnable {
|
||||
LiveConfigDatabaseBean bean;
|
||||
boolean isDownload = true;
|
||||
LiveApi api;
|
||||
String savePath;
|
||||
LiveConfigDatabaseBean config;
|
||||
BiliLiveDatabase database;
|
||||
LiveVideoDatabaseBean videoDatabaseBean;
|
||||
LiveRoomInfo roomInfo;
|
||||
|
||||
public VideoTask(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
|
||||
this.bean = bean;
|
||||
this.roomInfo = roomInfo;
|
||||
api = BiliLiveNetApiManager.getInstance().getApi(bean.getRecordUid());
|
||||
}
|
||||
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (roomInfo.getLiveStatus() == 1) {
|
||||
String time = DateUtils.format(new Date().getTime(), DATE_FORMAT_10_DASH);
|
||||
savePath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle()).getAbsolutePath();
|
||||
savePath += File.separator + "[" +
|
||||
DateUtils.format(new Date(),
|
||||
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv";
|
||||
record(bean, roomInfo);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
api.getRoomInfo(config.getRoomId().toString()).enqueue(new HttpCallback<LiveRoomInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomInfo response, String rawResponse) {
|
||||
if (response.getLiveStatus() == 1) {
|
||||
LiveVideoService.this.start(bean, false);
|
||||
} else {
|
||||
LiveVideoService.this.stop(bean.getRoomId().toString(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
|
||||
}
|
||||
});
|
||||
|
||||
}
|
||||
|
||||
private void record(LiveConfigDatabaseBean bean, LiveRoomInfo roomInfo) {
|
||||
this.config = bean;
|
||||
isDownload = true;
|
||||
LiveRoomConfig config = new LiveRoomConfig();
|
||||
config.setLoginUid(bean.getRecordUid());
|
||||
config.setRoomId(bean.getRoomId());
|
||||
config.setAnchorName(bean.getAnchorName());
|
||||
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
|
||||
config.setRoomInfo(roomInfo);
|
||||
config.setRootPath(bean.getRecordPath());
|
||||
database = new BiliLiveDatabase(config);
|
||||
api.getLiveRoomPlayInfo(
|
||||
bean.getRoomId().toString(),
|
||||
LiveProtocol.getAll(),
|
||||
LiveVideoFormat.getAll(),
|
||||
LiveVideoCodec.getAll(),
|
||||
LiveVideoDefinition.ORIGINAL.getValue()).enqueue(new HttpCallback<LiveRoomPlayInfo>() {
|
||||
@Override
|
||||
public void onResponse(Headers headers, int code, String status, LiveRoomPlayInfo response, String rawResponse) {
|
||||
LiveRoomPlayInfo.Codec codec = response.getPlayurlInfo().getPlayurl().getStream().get(0).getFormat().get(0).getCodec().get(0);
|
||||
String url = codec.getUrlInfo().get(0).getHost() + codec.getBaseUrl() + codec.getUrlInfo().get(0).getExtra();
|
||||
|
||||
if (bean.getRecordLiveModel() == 1) {
|
||||
javaRecord(url, response);
|
||||
} else {
|
||||
ffmpeg(url, response);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
throwable.printStackTrace();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void javaRecord(String url, LiveRoomPlayInfo playInfo) {
|
||||
|
||||
HttpDownloadUtils.download(new HttpDownloadUtils.Builder()
|
||||
.setUrl(url)
|
||||
.setPath(savePath)
|
||||
.setDownloadInterface(new DownloadInterface() {
|
||||
@Override
|
||||
public void onDownloadStart() {
|
||||
super.onDownloadStart();
|
||||
VideoTask.this.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes) {
|
||||
return isDownload;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(File file) {
|
||||
super.onDownload(file);
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(Exception e) {
|
||||
super.onError(e);
|
||||
stop();
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private void ffmpeg(String url, LiveRoomPlayInfo playInfo) {
|
||||
String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
|
||||
String cookie = "";
|
||||
LoginCookieDatabaseBean ck = BiliBiliLoginDatabase.getInstance().getCookie(config.getRecordUid());
|
||||
if (ck != null) {
|
||||
cookie = ck.toCookieString();
|
||||
}
|
||||
|
||||
FFmpegUtils command = new FFmpegUtils.Builder()
|
||||
.withParam("-user_agent", ConfigTools.getUserAgent())
|
||||
.withParam("-cookies", cookie)
|
||||
.withParam("-headers", "Referer: https://live.bilibili.com")
|
||||
// .withNotSymbolParam("-progress", "-")
|
||||
.withNotSymbolParam("-threads", "8")
|
||||
.withNotSymbolParam("-c:v", "copy")
|
||||
.withNotSymbolParam("-y", "")
|
||||
.build(ffmpegPath, url, savePath);
|
||||
System.out.println(command);
|
||||
try {
|
||||
command.start(new DownloadInterface() {
|
||||
@Override
|
||||
public void onDownloadStart() {
|
||||
super.onDownloadStart();
|
||||
VideoTask.this.onStart();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onDownloading(double soFarBytes, double totalBytes) {
|
||||
if (!isDownload) {
|
||||
command.stop();
|
||||
}
|
||||
return super.onDownloading(soFarBytes, totalBytes);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDownload(File file) {
|
||||
super.onDownload(file);
|
||||
System.err.println("下载完成 ");
|
||||
stop();
|
||||
}
|
||||
});
|
||||
|
||||
} catch (Exception e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private void onStart() {
|
||||
videoDatabaseBean = new LiveVideoDatabaseBean();
|
||||
videoDatabaseBean.setPath(savePath);
|
||||
videoDatabaseBean.setRoomInfoJson(JSONObject.toJSONString(roomInfo));
|
||||
videoDatabaseBean.setStartTime(new Date());
|
||||
database.addLiveInfo(videoDatabaseBean);
|
||||
}
|
||||
}
|
||||
|
||||
public List<VideoFilePath> getAllVideoPath() {
|
||||
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
||||
List<LiveConfigDatabaseBean> list = configDatabase.getAllConfig();
|
||||
configDatabase.close();
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
filePathList.addAll(getVideoFilePath(bean));
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public List<VideoFilePath> getVideoPath(String roomId) {
|
||||
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
||||
LiveConfigDatabaseBean bean = configDatabase.getConfig(new BigInteger(roomId));
|
||||
configDatabase.close();
|
||||
return new ArrayList<>(getVideoFilePath(bean));
|
||||
}
|
||||
|
||||
private List<VideoFilePath> getVideoFilePath(LiveConfigDatabaseBean configBean) {
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
String recordPath = configBean.getRecordPath() + File.separator + configBean.getAnchorName();
|
||||
File recordDir = new File(recordPath);
|
||||
if (recordDir.exists()) {
|
||||
|
||||
List<File> files = Tools.scanFile(recordDir).stream()
|
||||
.filter(file -> "live.db".equals(file.getName()))
|
||||
.toList();
|
||||
|
||||
if (!files.isEmpty()) {
|
||||
for (File db : files) {
|
||||
VideoFilePath path = createVideoRootFilePath(configBean, db);
|
||||
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId().toString()), db.getAbsolutePath());
|
||||
List<LiveVideoDatabaseBean> infos = database.getLiveInfos();
|
||||
database.close();
|
||||
path.setChildren(getVideoInfo(infos));
|
||||
filePathList.add(path);
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
private VideoFilePath createVideoRootFilePath(LiveConfigDatabaseBean config, File db) {
|
||||
VideoFilePath path = new VideoFilePath();
|
||||
path.setRoomId(config.getRoomId().toString());
|
||||
path.setCover(config.getAnchorFace());
|
||||
path.setName(config.getAnchorName());
|
||||
path.setParent(true);
|
||||
path.setPath(FileServerUtils.toUrl(db.getParent()));
|
||||
return path;
|
||||
}
|
||||
|
||||
private List<VideoFilePath> getVideoInfo(List<LiveVideoDatabaseBean> videoList) {
|
||||
List<VideoFilePath> filePathList = new ArrayList<>();
|
||||
for (LiveVideoDatabaseBean bean : videoList) {
|
||||
VideoFilePath path = new VideoFilePath();
|
||||
LiveRoomInfo roomInfo = JSONObject.parseObject(bean.getRoomInfoJson(), LiveRoomInfo.class);
|
||||
path.setRoomId(roomInfo.getRoomId().toString());
|
||||
path.setName(roomInfo.getTitle());
|
||||
path.setPath(DateFormatUtils.format(bean.getSql_time()));
|
||||
path.setCover(FileServerUtils.toUrl(new File(bean.getPath()).getParent() + File.separator + "poster.jpg"));
|
||||
path.setParent(false);
|
||||
path.setChildren(null);
|
||||
filePathList.add(path);
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LiveVideoService service = new LiveVideoService();
|
||||
List<VideoFilePath> path = service.getAllVideoPath();
|
||||
System.out.println("path.size() = " + path.size());
|
||||
System.out.println(JSONArray.toJSONString(path));
|
||||
}
|
||||
}
|
||||
@@ -27,6 +27,8 @@ import java.util.concurrent.TimeUnit;
|
||||
|
||||
@Service
|
||||
public class SystemService {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
SystemConfigDatabases databases = new SystemConfigDatabases();
|
||||
private ScheduledExecutorService timer;
|
||||
private ScheduledFuture<?> scheduled;
|
||||
@@ -50,6 +52,7 @@ public class SystemService {
|
||||
}
|
||||
scheduled = timer.scheduleAtFixedRate(() -> {
|
||||
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
|
||||
System.out.println("循环任务:"+list.size());
|
||||
for (LiveConfigDatabaseBean bean : list) {
|
||||
if (bean.isRecordDanmu() && bean.checkRecordDanmuTime()) {
|
||||
recordDanmu(bean);
|
||||
@@ -60,7 +63,10 @@ public class SystemService {
|
||||
}
|
||||
}, 0, getLoopTimer(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
scheduled.cancel(true);
|
||||
videoService.stopAll();
|
||||
}
|
||||
// 录制弹幕
|
||||
private void recordDanmu(LiveConfigDatabaseBean bean) {
|
||||
LiveRoomConfig config = new LiveRoomConfig();
|
||||
@@ -68,12 +74,13 @@ public class SystemService {
|
||||
config.setRoomId(bean.getRoomId());
|
||||
config.setAnchorName(bean.getAnchorName());
|
||||
config.setLogin(StringUtils.hasText(bean.getRecordUid()));
|
||||
WebSocketManager.getInstance().addRoom(config);
|
||||
config.setRootPath(bean.getRecordPath());
|
||||
WebSocketManager.getInstance().addRoom(config,false);
|
||||
}
|
||||
|
||||
// 录制视频
|
||||
private void recordVideo(LiveConfigDatabaseBean bean) {
|
||||
|
||||
videoService.start(bean, false);
|
||||
}
|
||||
|
||||
public static void main(String[] args) throws InterruptedException {
|
||||
@@ -85,4 +92,5 @@ public class SystemService {
|
||||
service.start();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@@ -56,7 +56,7 @@ public class BiliBiliLiveDatabasesManager extends SQLiteManager {
|
||||
"values (%d,%d,%d,%d,'%s','%s',%d,'%s')"
|
||||
,data.getModel()
|
||||
,data.getFontSize()
|
||||
,data.getFontColor()
|
||||
// ,data.getFontColor()
|
||||
,data.getTime()
|
||||
,data.getUCode()
|
||||
,data.getDanmu()
|
||||
@@ -87,7 +87,7 @@ public class BiliBiliLiveDatabasesManager extends SQLiteManager {
|
||||
data.setId(set.getInt("id"));
|
||||
data.setModel(set.getInt("model"));
|
||||
data.setFontSize(set.getInt("fontSize"));
|
||||
data.setFontColor(set.getInt("fontColor"));
|
||||
// data.setFontColor(set.getInt("fontColor"));
|
||||
data.setTime(set.getLong("time"));
|
||||
data.setUCode(set.getString("uCode"));
|
||||
data.setDanmu(set.getString("danmu"));
|
||||
|
||||
Reference in New Issue
Block a user