update
This commit is contained in:
@@ -81,7 +81,12 @@ public class LiveConfigController {
|
||||
@RequestMapping(value = "all", method = RequestMethod.GET)
|
||||
@ResponseBody
|
||||
public JSONObject getAllConfig(int page,int limit) {
|
||||
List<LiveConfigDatabaseBean> config = configService.getConfigs(page, limit);
|
||||
List<LiveConfigDatabaseBean> config ;
|
||||
if(page==-1||limit==-1) {
|
||||
config=configService.getAllConfig();
|
||||
}else{
|
||||
config=configService.getConfigs(page, limit);
|
||||
}
|
||||
if (config != null) {
|
||||
return ResultData.success(config,configService.getConfigCount());
|
||||
}
|
||||
|
||||
@@ -2,22 +2,18 @@ package com.yutou.bilibili.Controllers;
|
||||
|
||||
import com.alibaba.fastjson2.JSONObject;
|
||||
import com.yutou.bilibili.datas.ResultData;
|
||||
import com.yutou.bilibili.datas.VideoFilePath;
|
||||
import com.yutou.bilibili.services.LiveDanmuService;
|
||||
import com.yutou.bilibili.services.LiveService;
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import com.yutou.bilibili.services.LiveVideoDownloadService;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.val;
|
||||
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 LiveController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoService;
|
||||
@Resource
|
||||
LiveDanmuService danmuService;
|
||||
@Resource
|
||||
|
||||
@@ -1,13 +1,11 @@
|
||||
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 com.yutou.bilibili.services.LiveVideoDownloadService;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Controller;
|
||||
import org.springframework.web.bind.annotation.RequestMapping;
|
||||
@@ -18,7 +16,7 @@ import java.util.List;
|
||||
@Controller
|
||||
public class LiveVideoController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoService;
|
||||
|
||||
@RequestMapping("/live/video/list")
|
||||
@ResponseBody
|
||||
|
||||
@@ -5,17 +5,14 @@ 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 com.yutou.bilibili.services.LiveVideoDownloadService;
|
||||
import com.yutou.common.okhttp.HttpDownloadUtils;
|
||||
import com.yutou.common.utils.AppTools;
|
||||
import com.yutou.common.utils.Base64Tools;
|
||||
import com.yutou.common.utils.Log;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.val;
|
||||
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;
|
||||
@@ -34,16 +31,16 @@ import java.util.List;
|
||||
@Controller
|
||||
public class VideoFileController {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoService;
|
||||
|
||||
@ResponseBody
|
||||
@RequestMapping("/file/list")
|
||||
public JSONObject getFileList(String roomId) {
|
||||
public JSONObject getFileList(int page,int limit,String roomId) {
|
||||
List<VideoFilePath> list;
|
||||
if (StringUtils.hasText(roomId)) {
|
||||
list = videoService.getVideoPath(roomId);
|
||||
} else {
|
||||
list = videoService.getAllVideoPath();
|
||||
list = videoService.getAllVideoPath(page,limit);
|
||||
}
|
||||
return ResultData.success(list);
|
||||
}
|
||||
|
||||
@@ -1,21 +1,19 @@
|
||||
package com.yutou.bilibili.Tools;
|
||||
|
||||
import com.yutou.bilibili.services.LiveVideoService;
|
||||
import com.yutou.bilibili.services.LiveVideoDownloadService;
|
||||
import com.yutou.bilibili.services.SystemService;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
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;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
@Component
|
||||
public class ApplicationClose implements ApplicationListener<ContextClosedEvent> {
|
||||
@Resource
|
||||
SystemService systemConfigService;
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoService;
|
||||
@Override
|
||||
public void onApplicationEvent(ContextClosedEvent contextClosedEvent) {
|
||||
Log.i("服务结束");
|
||||
|
||||
@@ -8,6 +8,7 @@ import java.util.List;
|
||||
public class VideoFilePath {
|
||||
private String name;
|
||||
private String roomId;
|
||||
private String uid;
|
||||
private String path;
|
||||
private String cover;
|
||||
private boolean isParent;
|
||||
|
||||
@@ -27,7 +27,7 @@ public class LiveConfigService {
|
||||
if (!StringUtils.hasText(roomId)) {
|
||||
return null;
|
||||
}
|
||||
bean.setRoomId(new String(roomId));
|
||||
bean.setRoomId(roomId);
|
||||
try {
|
||||
LiveRoomInfo body = BiliLiveNetApiManager.getInstance().getApi(null).getRoomInfo(String.valueOf(bean.getRoomId())).execute().body().getData();
|
||||
MasterInfoBean infoBean = BiliLiveNetApiManager.getInstance().getApi(null).getMasterInfo(String.valueOf(body.getUid())).execute().body().getData();
|
||||
|
||||
@@ -6,11 +6,8 @@ import com.yutou.biliapi.bean.live.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.net.BiliLiveNetApiManager;
|
||||
import com.yutou.bilibili.datas.web.LiveData;
|
||||
import com.yutou.common.okhttp.HttpBody;
|
||||
import com.yutou.common.utils.AppTools;
|
||||
import com.yutou.common.utils.Log;
|
||||
import jakarta.annotation.Resource;
|
||||
import lombok.val;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.io.IOException;
|
||||
@@ -22,7 +19,7 @@ import java.util.List;
|
||||
public class LiveService {
|
||||
BiliLiveConfigDatabase liveConfigDatabase;
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoDownloadService;
|
||||
@Resource
|
||||
LiveDanmuService danmuService;
|
||||
LiveApi api;
|
||||
@@ -43,7 +40,7 @@ public class LiveService {
|
||||
liveData.setAnchorUid(config.getAnchorUid());
|
||||
liveData.setAnchorName(config.getAnchorName());
|
||||
liveData.setAnchorFace(config.getAnchorFace());
|
||||
liveData.setDownloadVideo(videoService.checkDownload(config.getRoomId()));
|
||||
liveData.setDownloadVideo(videoDownloadService.checkDownload(config.getRoomId()));
|
||||
liveData.setDanmu(danmuService.check(config.getRoomId()));
|
||||
try {
|
||||
LiveRoomInfo body = api.getRoomInfo(config.getRoomId()).execute().body().getData();
|
||||
|
||||
@@ -0,0 +1,369 @@
|
||||
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.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.Tools.FileServerUtils;
|
||||
import com.yutou.bilibili.Tools.LiveInfoNfoTools;
|
||||
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.record.AbsVideoRecord;
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import com.yutou.common.utils.Log;
|
||||
import okhttp3.Headers;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.io.File;
|
||||
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 LiveVideoDownloadService {
|
||||
private final ThreadPoolExecutor executor;
|
||||
private final List<String> userStopList = new ArrayList<>();//手动停止列表
|
||||
private final AbsVideoRecord videoRecord;
|
||||
|
||||
public LiveVideoDownloadService() {
|
||||
Log.i("初始化下载服务");
|
||||
videoRecord = new FFmpegUtils();
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
|
||||
public boolean checkDownload(String roomId) {
|
||||
return videoRecord.check(roomId);
|
||||
}
|
||||
|
||||
public void clearUserStopList() {
|
||||
userStopList.clear();
|
||||
}
|
||||
|
||||
public void start(LiveConfigDatabaseBean bean, boolean isUser) {
|
||||
if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.remove(bean.getRoomId().toString());
|
||||
}
|
||||
if (videoRecord.check(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
Log.i("移除下载");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
Log.i("移除下载");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void stop(String roomId, boolean isUser) {
|
||||
if (isUser) {
|
||||
userStopList.add(roomId);
|
||||
}
|
||||
videoRecord.kill(roomId);
|
||||
}
|
||||
|
||||
|
||||
public JSONArray getDownloadTasks() {
|
||||
JSONArray array = new JSONArray();
|
||||
array.addAll(videoRecord.getRoomIds());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
videoRecord.killAll();
|
||||
}
|
||||
|
||||
private class VideoTask implements Runnable {
|
||||
LiveConfigDatabaseBean bean;
|
||||
boolean isDownload = true;
|
||||
LiveApi api;
|
||||
String savePath;
|
||||
File rootPath;
|
||||
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);
|
||||
rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle());
|
||||
savePath = rootPath.getAbsolutePath() + File.separator + "[" +
|
||||
DateUtils.format(new Date(),
|
||||
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv";
|
||||
if (!rootPath.exists()) {
|
||||
rootPath.mkdirs();
|
||||
}
|
||||
record(bean, roomInfo);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
videoRecord.kill(bean.getRoomId().toString());
|
||||
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) {
|
||||
LiveVideoDownloadService.this.start(bean, false);
|
||||
} else {
|
||||
LiveVideoDownloadService.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);
|
||||
HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(roomInfo.getKeyframe())
|
||||
.setPath(rootPath.getAbsolutePath())
|
||||
.setFileName("poster.jpg"));
|
||||
LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo"));
|
||||
saveLiveInfo(roomInfo);
|
||||
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) {
|
||||
Log.e(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.Builder builder = new FFmpegUtils.Builder()
|
||||
.withParam("-user_agent", ConfigTools.getUserAgent())
|
||||
.withParam("-headers", "Referer: https://live.bilibili.com")
|
||||
// .withNotSymbolParam("-progress", "-")
|
||||
.withNotSymbolParam("-threads", "8")
|
||||
.withNotSymbolParam("-c:v", "copy")
|
||||
.withNotSymbolParam("-y", "");
|
||||
if (ck != null) {
|
||||
builder = builder.withParam("-cookies", cookie);
|
||||
}
|
||||
FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath);
|
||||
Log.i(command.getCommand());
|
||||
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);
|
||||
Log.e("下载完成 ");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLiveInfo(LiveRoomInfo roomInfo) {
|
||||
|
||||
}
|
||||
|
||||
public List<VideoFilePath> getAllVideoPath(int page, int limit) {
|
||||
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
||||
List<LiveConfigDatabaseBean> list = configDatabase.getConfigs(page, limit);
|
||||
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(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);
|
||||
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId()), recordDir.getAbsolutePath());
|
||||
VideoFilePath path = createVideoRootFilePath(configBean, recordDir);
|
||||
if (recordDir.exists()) {
|
||||
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());
|
||||
path.setCover(config.getAnchorFace());
|
||||
path.setName(config.getAnchorName());
|
||||
path.setUid(config.getAnchorUid());
|
||||
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());
|
||||
path.setName(roomInfo.getTitle());
|
||||
path.setUid(roomInfo.getUid());
|
||||
path.setPath(DateFormatUtils.format(bean.getSql_time()));
|
||||
path.setCover(roomInfo.getKeyframe());
|
||||
path.setParent(false);
|
||||
path.setChildren(null);
|
||||
filePathList.add(path);
|
||||
}
|
||||
return filePathList;
|
||||
}
|
||||
|
||||
public static void main(String[] args) {
|
||||
LiveVideoDownloadService service = new LiveVideoDownloadService();
|
||||
List<VideoFilePath> path = service.getAllVideoPath(1, 8);
|
||||
Log.i("path.size() = " + path.size());
|
||||
Log.i(JSONArray.toJSONString(path));
|
||||
}
|
||||
}
|
||||
@@ -1,371 +1,8 @@
|
||||
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.LiveInfoNfoTools;
|
||||
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.record.AbsVideoRecord;
|
||||
import com.yutou.common.utils.ConfigTools;
|
||||
import com.yutou.common.utils.FFmpegUtils;
|
||||
import com.yutou.common.utils.Log;
|
||||
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 {
|
||||
private final ThreadPoolExecutor executor;
|
||||
private final List<String> userStopList = new ArrayList<>();//手动停止列表
|
||||
private final AbsVideoRecord videoRecord;
|
||||
|
||||
public LiveVideoService() {
|
||||
Log.i("初始化下载服务");
|
||||
videoRecord = new FFmpegUtils();
|
||||
executor = new ThreadPoolExecutor(2, 4, Long.MAX_VALUE, TimeUnit.SECONDS, new ArrayBlockingQueue<Runnable>(100));
|
||||
}
|
||||
|
||||
public boolean checkDownload(String roomId) {
|
||||
return videoRecord.check(roomId);
|
||||
}
|
||||
public void clearUserStopList() {
|
||||
userStopList.clear();
|
||||
}
|
||||
|
||||
public void start(LiveConfigDatabaseBean bean, boolean isUser) {
|
||||
if (!isUser && userStopList.contains(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
if (isUser) {
|
||||
userStopList.remove(bean.getRoomId().toString());
|
||||
}
|
||||
if (videoRecord.check(bean.getRoomId().toString())) {
|
||||
return;
|
||||
}
|
||||
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);
|
||||
} else {
|
||||
Log.i("移除下载");
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFailure(Throwable throwable) {
|
||||
Log.i("移除下载");
|
||||
}
|
||||
});
|
||||
|
||||
|
||||
}
|
||||
|
||||
public void stop(String roomId, boolean isUser) {
|
||||
if (isUser) {
|
||||
userStopList.add(roomId);
|
||||
}
|
||||
videoRecord.kill(roomId);
|
||||
}
|
||||
|
||||
|
||||
public JSONArray getDownloadTasks() {
|
||||
JSONArray array = new JSONArray();
|
||||
array.addAll(videoRecord.getRoomIds());
|
||||
return array;
|
||||
}
|
||||
|
||||
public void stopAll() {
|
||||
videoRecord.killAll();
|
||||
}
|
||||
|
||||
private class VideoTask implements Runnable {
|
||||
LiveConfigDatabaseBean bean;
|
||||
boolean isDownload = true;
|
||||
LiveApi api;
|
||||
String savePath;
|
||||
File rootPath;
|
||||
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);
|
||||
rootPath = new File(bean.getRecordPath() + File.separator + bean.getAnchorName() + File.separator + time + File.separator + roomInfo.getTitle());
|
||||
savePath = rootPath.getAbsolutePath() + File.separator + "[" +
|
||||
DateUtils.format(new Date(),
|
||||
"yyyy-MM-dd HH-mm-ss") + "]" + roomInfo.getTitle() + ".flv";
|
||||
if (!rootPath.exists()) {
|
||||
rootPath.mkdirs();
|
||||
}
|
||||
record(bean, roomInfo);
|
||||
} else {
|
||||
stop();
|
||||
}
|
||||
}
|
||||
|
||||
private void stop() {
|
||||
videoRecord.kill(bean.getRoomId().toString());
|
||||
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);
|
||||
HttpDownloadUtils.download(new HttpDownloadUtils.Builder().setUrl(roomInfo.getKeyframe())
|
||||
.setPath(rootPath.getAbsolutePath())
|
||||
.setFileName("poster.jpg"));
|
||||
LiveInfoNfoTools.saveLiveInfoNfo(roomInfo, rootPath.getAbsolutePath(), new File(savePath).getName().replace(".flv", ".nfo"));
|
||||
saveLiveInfo(roomInfo);
|
||||
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) {
|
||||
Log.e(throwable);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
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.Builder builder = new FFmpegUtils.Builder()
|
||||
.withParam("-user_agent", ConfigTools.getUserAgent())
|
||||
.withParam("-headers", "Referer: https://live.bilibili.com")
|
||||
// .withNotSymbolParam("-progress", "-")
|
||||
.withNotSymbolParam("-threads", "8")
|
||||
.withNotSymbolParam("-c:v", "copy")
|
||||
.withNotSymbolParam("-y", "");
|
||||
if (ck != null) {
|
||||
builder = builder.withParam("-cookies", cookie);
|
||||
}
|
||||
FFmpegUtils command = builder.build(config.getRoomId(), ffmpegPath, url, savePath);
|
||||
Log.i(command.getCommand());
|
||||
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);
|
||||
Log.e("下载完成 ");
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
private void saveLiveInfo(LiveRoomInfo roomInfo) {
|
||||
|
||||
}
|
||||
|
||||
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 String(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()) {
|
||||
|
||||
BiliLiveDatabase database = new BiliLiveDatabase(LiveRoomConfig.buildConfig(configBean.getRoomId().toString()), recordDir.getAbsolutePath());
|
||||
VideoFilePath path = createVideoRootFilePath(configBean, recordDir);
|
||||
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(roomInfo.getKeyframe());
|
||||
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();
|
||||
Log.i("path.size() = " + path.size());
|
||||
Log.i(JSONArray.toJSONString(path));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,27 +1,16 @@
|
||||
package com.yutou.bilibili.services;
|
||||
|
||||
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.database.LiveConfigDatabaseBean;
|
||||
import com.yutou.biliapi.databases.BiliLiveConfigDatabase;
|
||||
import com.yutou.biliapi.net.BiliLiveNetApiManager;
|
||||
import com.yutou.biliapi.net.WebSocketManager;
|
||||
import com.yutou.bilibili.Tools.DateFormatUtils;
|
||||
import com.yutou.bilibili.databases.SystemConfigDatabases;
|
||||
import com.yutou.bilibili.datas.SystemConfigDatabaseBean;
|
||||
import com.yutou.common.okhttp.HttpBody;
|
||||
import com.yutou.common.utils.Log;
|
||||
import jakarta.annotation.Resource;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.util.StringUtils;
|
||||
import retrofit2.Response;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.Date;
|
||||
import java.util.List;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
@@ -30,7 +19,7 @@ import java.util.concurrent.TimeUnit;
|
||||
@Service
|
||||
public class SystemService {
|
||||
@Resource
|
||||
LiveVideoService videoService;
|
||||
LiveVideoDownloadService videoService;
|
||||
@Resource
|
||||
LiveDanmuService danmuService;
|
||||
SystemConfigDatabases databases = new SystemConfigDatabases();
|
||||
@@ -59,7 +48,7 @@ public class SystemService {
|
||||
scheduled = timer.scheduleAtFixedRate(() -> {
|
||||
List<LiveConfigDatabaseBean> list = liveConfigDatabase.getAllConfig();
|
||||
Log.i("循环任务:" + list.size());
|
||||
if(DateFormatUtils.checkTime(null,resetTimer)){
|
||||
if (DateFormatUtils.checkTime(null, resetTimer)) {
|
||||
videoService.clearUserStopList();
|
||||
danmuService.clearUserList();
|
||||
}
|
||||
@@ -67,6 +56,8 @@ public class SystemService {
|
||||
try {
|
||||
if (bean.isRecordDanmu() && bean.checkRecordDanmuTime()) {
|
||||
recordDanmu(bean);
|
||||
} else if (!bean.checkRecordDanmuTime()) {
|
||||
stopRecordDanmu(bean);
|
||||
}
|
||||
if (bean.isRecordLive() && bean.checkRecordLiveTime()) {
|
||||
recordVideo(bean);
|
||||
@@ -79,6 +70,12 @@ public class SystemService {
|
||||
}, 0, getLoopTimer(), TimeUnit.MILLISECONDS);
|
||||
}
|
||||
|
||||
private void stopRecordDanmu(LiveConfigDatabaseBean bean) {
|
||||
if (!videoService.checkDownload(bean.getRoomId())) {
|
||||
danmuService.stop(bean.getRoomId(), false);
|
||||
}
|
||||
}
|
||||
|
||||
public void stop() {
|
||||
scheduled.cancel(true);
|
||||
videoService.stopAll();
|
||||
|
||||
Reference in New Issue
Block a user