389 lines
16 KiB
Java
389 lines
16 KiB
Java
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 VideoFilePath getVideoPath(String roomId) {
|
|
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
|
LiveConfigDatabaseBean bean = configDatabase.getConfig(roomId);
|
|
configDatabase.close();
|
|
return getVideoFilePath(bean);
|
|
}
|
|
|
|
private VideoFilePath getVideoFilePath(LiveConfigDatabaseBean configBean) {
|
|
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));
|
|
}
|
|
return path;
|
|
}
|
|
|
|
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 Map<String, List<VideoFilePath>> getVideoInfo(List<LiveVideoDatabaseBean> videoList) {
|
|
Map<String, List<VideoFilePath>> map = new HashMap<>();
|
|
for (LiveVideoDatabaseBean bean : videoList) {
|
|
String date = DateFormatUtils.format(bean.getSql_time(), "yyyy-MM-dd");
|
|
if (!map.containsKey(date)) {
|
|
map.put(date, new ArrayList<>());
|
|
}
|
|
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(String.valueOf(bean.getSql_time().getTime()));
|
|
path.setCover(roomInfo.getKeyframe());
|
|
path.setParent(false);
|
|
path.setChildren(null);
|
|
map.get(date).add(path);
|
|
}
|
|
return map;
|
|
}
|
|
|
|
public String getVideoPlay(String roomId, String videoId) {
|
|
String ffmpegPath = ConfigTools.load(ConfigTools.CONFIG, "ffmpeg", String.class);
|
|
BiliLiveConfigDatabase configDatabase = new BiliLiveConfigDatabase();
|
|
LiveConfigDatabaseBean config = configDatabase.getConfig(roomId);
|
|
String recordPath = config.getRecordPath() + File.separator + config.getAnchorName();
|
|
configDatabase.close();
|
|
LiveVideoDatabaseBean videoInfo = null;
|
|
BiliLiveDatabase liveDatabase = new BiliLiveDatabase(LiveRoomConfig.buildConfig(roomId), new File(recordPath).getAbsolutePath());
|
|
for (LiveVideoDatabaseBean info : liveDatabase.getLiveInfos()) {
|
|
if (videoId.trim().equals(String.valueOf(info.getSql_time().getTime()))) {
|
|
videoInfo = info;
|
|
break;
|
|
}
|
|
}
|
|
if(videoInfo != null) {
|
|
FFmpegUtils ffmpeg = FFmpegUtils.segment(videoId, ffmpegPath, new File(videoInfo.getPath()), ConfigTools.load(ConfigTools.CONFIG, "outVideoPath", String.class));
|
|
System.out.println(ffmpeg.getCommand());
|
|
ffmpeg.start(new DownloadInterface() {
|
|
@Override
|
|
public void onDownload(File file) {
|
|
super.onDownload(file);
|
|
|
|
}
|
|
});
|
|
return ffmpeg.getOutputFilePath();
|
|
}
|
|
return null;
|
|
}
|
|
|
|
public static void main(String[] args) {
|
|
LiveVideoDownloadService service=new LiveVideoDownloadService();
|
|
String play = service.getVideoPlay("17961", "1730363029293");
|
|
System.out.println(play);
|
|
}
|
|
}
|