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 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(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() { @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() { @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() { @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 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> getVideoInfo(List videoList) { Map> 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); } }