mirror of
https://github.com/Xinrea/bili-shadowreplay.git
synced 2025-11-24 20:15:34 +08:00
fix: progress and danmu events
This commit is contained in:
@@ -29,4 +29,4 @@ BiliBili ShadowReplay 是一个缓存直播并进行实时编辑投稿的工具
|
||||
## 赞助
|
||||
|
||||
<!-- markdownlint-disable MD033 -->
|
||||
<img src="docs/public/images/donate.png" alt="donate" width="300" style="display: block; margin: 0 auto;">
|
||||
<img src="docs/public/images/donate.png" alt="donate" width="300">
|
||||
|
||||
@@ -52,6 +52,9 @@
|
||||
},
|
||||
{
|
||||
"url": "http://tauri.localhost/*"
|
||||
},
|
||||
{
|
||||
"url": "http://localhost:8054/*"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -1 +1 @@
|
||||
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main","Live*","Clip*"],"permissions":["core:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["**"]},"core:window:default","core:window:allow-start-dragging","core:window:allow-close","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-set-title","sql:allow-execute","shell:allow-open","dialog:allow-open","dialog:allow-save","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm",{"identifier":"http:default","allow":[{"url":"https://*.hdslb.com/"},{"url":"https://afdian.com/"},{"url":"https://*.afdiancdn.com/"},{"url":"https://*.douyin.com/"},{"url":"https://*.douyinpic.com/"},{"url":"http://tauri.localhost/*"}]},"dialog:default","shell:default","fs:default","http:default","sql:default","os:default","notification:default","dialog:default","fs:default","http:default","shell:default","sql:default","os:default","dialog:default","deep-link:default"]}}
|
||||
{"migrated":{"identifier":"migrated","description":"permissions that were migrated from v1","local":true,"windows":["main","Live*","Clip*"],"permissions":["core:default","fs:allow-read-file","fs:allow-write-file","fs:allow-read-dir","fs:allow-copy-file","fs:allow-mkdir","fs:allow-remove","fs:allow-remove","fs:allow-rename","fs:allow-exists",{"identifier":"fs:scope","allow":["**"]},"core:window:default","core:window:allow-start-dragging","core:window:allow-close","core:window:allow-minimize","core:window:allow-maximize","core:window:allow-unmaximize","core:window:allow-set-title","sql:allow-execute","shell:allow-open","dialog:allow-open","dialog:allow-save","dialog:allow-message","dialog:allow-ask","dialog:allow-confirm",{"identifier":"http:default","allow":[{"url":"https://*.hdslb.com/"},{"url":"https://afdian.com/"},{"url":"https://*.afdiancdn.com/"},{"url":"https://*.douyin.com/"},{"url":"https://*.douyinpic.com/"},{"url":"http://tauri.localhost/*"},{"url":"http://localhost:8054/*"}]},"dialog:default","shell:default","fs:default","http:default","sql:default","os:default","notification:default","dialog:default","fs:default","http:default","shell:default","sql:default","os:default","dialog:default","deep-link:default"]}}
|
||||
@@ -29,13 +29,10 @@ pub async fn create_websocket_server(state: State) -> SocketIoLayer {
|
||||
Ok(event) => {
|
||||
let (event_type, message) = match event {
|
||||
Event::ProgressUpdate { id, content } => (
|
||||
"progress",
|
||||
"progress-update",
|
||||
json!({
|
||||
"event": "progress-update",
|
||||
"data": {
|
||||
"id": id,
|
||||
"content": content
|
||||
}
|
||||
}),
|
||||
),
|
||||
Event::ProgressFinished {
|
||||
@@ -43,25 +40,19 @@ pub async fn create_websocket_server(state: State) -> SocketIoLayer {
|
||||
success,
|
||||
message,
|
||||
} => (
|
||||
"progress",
|
||||
"progress-finished",
|
||||
json!({
|
||||
"event": "progress-finished",
|
||||
"data": {
|
||||
"id": id,
|
||||
"success": success,
|
||||
"message": message
|
||||
}
|
||||
}),
|
||||
),
|
||||
Event::DanmuReceived { room, ts, content } => (
|
||||
"danmu",
|
||||
json!({
|
||||
"event": "danmu-received",
|
||||
"data": {
|
||||
"room": room,
|
||||
"ts": ts,
|
||||
"content": content
|
||||
}
|
||||
}),
|
||||
),
|
||||
};
|
||||
|
||||
@@ -76,7 +76,10 @@ impl EventEmitter {
|
||||
match event {
|
||||
Event::ProgressUpdate { id, content } => {
|
||||
self.app_handle
|
||||
.emit("progress-update", UpdateEvent { id, content })
|
||||
.emit(
|
||||
&format!("progress-update:{}", id),
|
||||
UpdateEvent { id, content },
|
||||
)
|
||||
.unwrap();
|
||||
}
|
||||
Event::ProgressFinished {
|
||||
@@ -86,7 +89,7 @@ impl EventEmitter {
|
||||
} => {
|
||||
self.app_handle
|
||||
.emit(
|
||||
"progress-finished",
|
||||
&format!("progress-finished:{}", id),
|
||||
FinishEvent {
|
||||
id,
|
||||
success: *success,
|
||||
|
||||
@@ -173,30 +173,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
const update_listener = listen<ProgressUpdate>(`progress-update`, (e) => {
|
||||
let event_id = e.payload.id;
|
||||
if (event_id === current_clip_event_id) {
|
||||
update_clip_prompt(e.payload.content);
|
||||
}
|
||||
});
|
||||
const finished_listener = listen<ProgressFinished>(
|
||||
`progress-finished`,
|
||||
(e) => {
|
||||
let event_id = e.payload.id;
|
||||
if (event_id === current_clip_event_id) {
|
||||
update_clip_prompt(`生成切片`);
|
||||
if (!e.payload.success) {
|
||||
alert("请检查 ffmpeg 是否配置正确:" + e.payload.message);
|
||||
}
|
||||
current_clip_event_id = null;
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
onDestroy(() => {
|
||||
update_listener?.then((fn) => fn());
|
||||
finished_listener?.then((fn) => fn());
|
||||
|
||||
// 清理滚动定时器
|
||||
if (scroll_timeout) {
|
||||
clearTimeout(scroll_timeout);
|
||||
@@ -328,6 +305,25 @@
|
||||
update_clip_prompt(`切片生成中`);
|
||||
let event_id = generateEventId();
|
||||
current_clip_event_id = event_id;
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${event_id}`,
|
||||
(e) => {
|
||||
update_clip_prompt(e.payload.content);
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${event_id}`,
|
||||
(e) => {
|
||||
update_clip_prompt(`生成切片`);
|
||||
if (!e.payload.success) {
|
||||
alert("请检查 ffmpeg 是否配置正确:" + e.payload.message);
|
||||
}
|
||||
current_clip_event_id = null;
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
let new_video = (await clipRange(event_id, {
|
||||
title: archive.title,
|
||||
note: clip_note,
|
||||
|
||||
@@ -55,47 +55,6 @@
|
||||
return size.toFixed(decimals) + " " + units[unitIndex];
|
||||
}
|
||||
|
||||
// 进度监听器
|
||||
const progressUpdateListener = listen<ProgressUpdate>(
|
||||
"progress-update",
|
||||
(e) => {
|
||||
if (e.payload.id === currentImportEventId) {
|
||||
importProgress = e.payload.content;
|
||||
|
||||
// 从进度文本中提取当前文件索引
|
||||
const match = importProgress.match(/正在导入第(\d+)个/);
|
||||
if (match) {
|
||||
currentFileIndex = parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
const progressFinishedListener = listen<ProgressFinished>(
|
||||
"progress-finished",
|
||||
(e) => {
|
||||
if (e.payload.id === currentImportEventId) {
|
||||
if (e.payload.success) {
|
||||
// 导入成功,关闭对话框并刷新列表
|
||||
showDialog = false;
|
||||
selectedFilePath = null;
|
||||
selectedFileName = "";
|
||||
selectedFileSize = 0;
|
||||
videoTitle = "";
|
||||
resetBatchImportState();
|
||||
dispatch("imported");
|
||||
} else {
|
||||
alert("导入失败: " + e.payload.message);
|
||||
resetBatchImportState();
|
||||
}
|
||||
// 无论成功失败都要重置状态
|
||||
importing = false;
|
||||
currentImportEventId = null;
|
||||
importProgress = "";
|
||||
}
|
||||
}
|
||||
);
|
||||
|
||||
// 连接恢复时检查任务状态
|
||||
async function checkTaskStatus() {
|
||||
if (!currentImportEventId || !importing) return;
|
||||
@@ -114,11 +73,6 @@
|
||||
}
|
||||
}
|
||||
|
||||
onDestroy(() => {
|
||||
progressUpdateListener?.then((fn) => fn());
|
||||
progressFinishedListener?.then((fn) => fn());
|
||||
});
|
||||
|
||||
async function handleFileSelect() {
|
||||
if (TAURI_ENV) {
|
||||
// Tauri模式:使用文件对话框,支持多选
|
||||
@@ -396,13 +350,49 @@
|
||||
const eventId = "batch_import_" + Date.now();
|
||||
currentImportEventId = eventId;
|
||||
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${eventId}`,
|
||||
(e) => {
|
||||
importProgress = e.payload.content;
|
||||
|
||||
// 从进度文本中提取当前文件索引
|
||||
const match = importProgress.match(/正在导入第(\d+)个/);
|
||||
if (match) {
|
||||
currentFileIndex = parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${eventId}`,
|
||||
(e) => {
|
||||
if (e.payload.success) {
|
||||
// 导入成功,关闭对话框并刷新列表
|
||||
showDialog = false;
|
||||
selectedFilePath = null;
|
||||
selectedFileName = "";
|
||||
selectedFileSize = 0;
|
||||
videoTitle = "";
|
||||
resetBatchImportState();
|
||||
dispatch("imported");
|
||||
} else {
|
||||
alert("导入失败: " + e.payload.message);
|
||||
resetBatchImportState();
|
||||
}
|
||||
// 无论成功失败都要重置状态
|
||||
importing = false;
|
||||
currentImportEventId = null;
|
||||
importProgress = "";
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
|
||||
await invoke("batch_import_external_videos", {
|
||||
eventId: eventId,
|
||||
filePaths: selectedFiles,
|
||||
roomId: roomId || 0,
|
||||
});
|
||||
|
||||
// 注意:成功处理在 progressFinishedListener 中进行
|
||||
} catch (error) {
|
||||
console.error("批量导入失败:", error);
|
||||
alert("批量导入失败: " + error);
|
||||
@@ -433,6 +423,44 @@
|
||||
const eventId = "import_" + Date.now();
|
||||
currentImportEventId = eventId;
|
||||
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${eventId}`,
|
||||
(e) => {
|
||||
importProgress = e.payload.content;
|
||||
|
||||
// 从进度文本中提取当前文件索引
|
||||
const match = importProgress.match(/正在导入第(\d+)个/);
|
||||
if (match) {
|
||||
currentFileIndex = parseInt(match[1]);
|
||||
}
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${eventId}`,
|
||||
(e) => {
|
||||
if (e.payload.success) {
|
||||
// 导入成功,关闭对话框并刷新列表
|
||||
showDialog = false;
|
||||
selectedFilePath = null;
|
||||
selectedFileName = "";
|
||||
selectedFileSize = 0;
|
||||
videoTitle = "";
|
||||
resetBatchImportState();
|
||||
dispatch("imported");
|
||||
} else {
|
||||
alert("导入失败: " + e.payload.message);
|
||||
resetBatchImportState();
|
||||
}
|
||||
// 无论成功失败都要重置状态
|
||||
importing = false;
|
||||
currentImportEventId = null;
|
||||
importProgress = "";
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
|
||||
await invoke("import_external_video", {
|
||||
eventId: eventId,
|
||||
filePath: selectedFilePath,
|
||||
|
||||
@@ -413,15 +413,11 @@ ${mediaPlaylistUrl}`;
|
||||
}
|
||||
|
||||
// listen to danmaku event
|
||||
await listen("danmu", (event: { payload: DanmuEntry }) => {
|
||||
await listen(`danmu:${room_id}`, (event: { payload: DanmuEntry }) => {
|
||||
if (global_offset == 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.payload.room != room_id) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (event.payload.ts < global_offset * 1000) {
|
||||
log.error("invalid danmu ts:", event.payload.ts, global_offset);
|
||||
return;
|
||||
|
||||
@@ -178,63 +178,7 @@
|
||||
}
|
||||
});
|
||||
|
||||
const update_listener = listen<ProgressUpdate>(`progress-update`, (e) => {
|
||||
let event_id = e.payload.id;
|
||||
console.log(e.payload);
|
||||
if (event_id == current_encode_event_id) {
|
||||
update_encode_prompt(e.payload.content);
|
||||
} else if (event_id == current_generate_event_id) {
|
||||
update_generate_prompt(e.payload.content);
|
||||
} else if (event_id === current_post_event_id) {
|
||||
update_post_prompt(e.payload.content);
|
||||
} else if (event_id === current_clip_event_id) {
|
||||
update_clip_prompt(e.payload.content);
|
||||
}
|
||||
});
|
||||
|
||||
const finish_listener = listen<ProgressFinished>(`progress-finished`, (e) => {
|
||||
let event_id = e.payload.id;
|
||||
if (event_id == current_encode_event_id) {
|
||||
update_encode_prompt(`压制字幕`);
|
||||
if (!e.payload.success) {
|
||||
alert("压制失败: " + e.payload.message);
|
||||
}
|
||||
current_encode_event_id = null;
|
||||
} else if (event_id == current_generate_event_id) {
|
||||
update_generate_prompt(`AI 生成字幕`);
|
||||
if (!e.payload.success) {
|
||||
alert("生成字幕失败: " + e.payload.message);
|
||||
}
|
||||
current_generate_event_id = null;
|
||||
} else if (event_id === current_post_event_id) {
|
||||
update_post_prompt(`投稿`);
|
||||
if (!e.payload.success) {
|
||||
alert(e.payload.message);
|
||||
}
|
||||
current_post_event_id = null;
|
||||
} else if (event_id === current_clip_event_id) {
|
||||
update_clip_prompt(`生成切片`);
|
||||
if (e.payload.success) {
|
||||
// 切片生成成功,刷新视频列表
|
||||
if (onVideoListUpdate) {
|
||||
onVideoListUpdate();
|
||||
}
|
||||
// 重置切片设置
|
||||
clipStartTime = 0;
|
||||
clipEndTime = 0;
|
||||
clipTitle = "";
|
||||
clipTimesSet = false; // 重置标记
|
||||
} else {
|
||||
alert("切片生成失败: " + e.payload.message);
|
||||
}
|
||||
current_clip_event_id = null;
|
||||
clipping = false;
|
||||
}
|
||||
});
|
||||
|
||||
onDestroy(() => {
|
||||
update_listener?.then((fn) => fn());
|
||||
finish_listener?.then((fn) => fn());
|
||||
// 清理窗口关闭事件监听器
|
||||
if (windowCloseUnlisten) {
|
||||
windowCloseUnlisten();
|
||||
@@ -272,6 +216,28 @@
|
||||
current_post_event_id = event_id;
|
||||
|
||||
update_post_prompt(`投稿上传中`);
|
||||
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${event_id}`,
|
||||
(e) => {
|
||||
update_post_prompt(e.payload.content);
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${event_id}`,
|
||||
(e) => {
|
||||
update_post_prompt(`投稿`);
|
||||
if (!e.payload.success) {
|
||||
alert(e.payload.message);
|
||||
}
|
||||
|
||||
current_post_event_id = null;
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
|
||||
// update profile in local storage
|
||||
window.localStorage.setItem("profile-" + roomId, JSON.stringify(profile));
|
||||
invoke("upload_procedure", {
|
||||
@@ -426,6 +392,26 @@
|
||||
async function generateSubtitles() {
|
||||
if (video?.file) {
|
||||
current_generate_event_id = generateEventId();
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${current_generate_event_id}`,
|
||||
(e) => {
|
||||
update_generate_prompt(e.payload.content);
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${current_generate_event_id}`,
|
||||
(e) => {
|
||||
update_generate_prompt(`AI 生成字幕`);
|
||||
if (!e.payload.success) {
|
||||
alert("生成字幕失败: " + e.payload.message);
|
||||
}
|
||||
|
||||
current_generate_event_id = null;
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
const savedSubtitles = (await invoke("generate_video_subtitle", {
|
||||
eventId: current_generate_event_id,
|
||||
id: video.id,
|
||||
@@ -614,6 +600,37 @@
|
||||
|
||||
clipping = true;
|
||||
current_clip_event_id = generateEventId();
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${current_clip_event_id}`,
|
||||
(e) => {
|
||||
update_clip_prompt(e.payload.content);
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${current_clip_event_id}`,
|
||||
(e) => {
|
||||
update_clip_prompt(`生成切片`);
|
||||
if (e.payload.success) {
|
||||
// 切片生成成功,刷新视频列表
|
||||
if (onVideoListUpdate) {
|
||||
onVideoListUpdate();
|
||||
}
|
||||
// 重置切片设置
|
||||
clipStartTime = 0;
|
||||
clipEndTime = 0;
|
||||
clipTitle = "";
|
||||
clipTimesSet = false; // 重置标记
|
||||
} else {
|
||||
alert("切片生成失败: " + e.payload.message);
|
||||
}
|
||||
clipping = false;
|
||||
|
||||
current_clip_event_id = null;
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
|
||||
try {
|
||||
await invoke("clip_video", {
|
||||
@@ -1079,6 +1096,26 @@
|
||||
await saveSubtitles();
|
||||
const event_id = generateEventId();
|
||||
current_encode_event_id = event_id;
|
||||
const clear_update_listener = await listen(
|
||||
`progress-update:${event_id}`,
|
||||
(e) => {
|
||||
update_encode_prompt(e.payload.content);
|
||||
}
|
||||
);
|
||||
const clear_finished_listener = await listen(
|
||||
`progress-finished:${event_id}`,
|
||||
(e) => {
|
||||
update_encode_prompt(`压制字幕`);
|
||||
if (!e.payload.success) {
|
||||
alert("压制失败: " + e.payload.message);
|
||||
}
|
||||
|
||||
current_encode_event_id = null;
|
||||
|
||||
clear_update_listener();
|
||||
clear_finished_listener();
|
||||
}
|
||||
);
|
||||
const result = await invoke("encode_video_subtitle", {
|
||||
eventId: event_id,
|
||||
id: video.id,
|
||||
|
||||
@@ -204,17 +204,15 @@ function createSocket() {
|
||||
});
|
||||
|
||||
// 监听服务器发送的事件
|
||||
socket.on("progress", (data) => {
|
||||
const eventType = data.event || "message";
|
||||
|
||||
socket.on("progress-finished", (data) => {
|
||||
const eventType = `progress-finished:${data.id}`;
|
||||
// 触发对应的事件监听器
|
||||
const listeners = eventListeners.get(eventType);
|
||||
if (listeners) {
|
||||
listeners.forEach((callback) => {
|
||||
try {
|
||||
callback({
|
||||
type: eventType,
|
||||
payload: data.data,
|
||||
payload: data,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(
|
||||
@@ -226,15 +224,28 @@ function createSocket() {
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("progress-update", (data) => {
|
||||
const eventType = `progress-update:${data.id}`;
|
||||
const listeners = eventListeners.get(eventType);
|
||||
if (listeners) {
|
||||
listeners.forEach((callback) => {
|
||||
callback({
|
||||
payload: data,
|
||||
});
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
socket.on("danmu", (data) => {
|
||||
// 触发对应的事件监听器
|
||||
const listeners = eventListeners.get("danmu");
|
||||
console.log("danmu:", data);
|
||||
const room_id = data.room;
|
||||
const listeners = eventListeners.get(`danmu:${room_id}`);
|
||||
if (listeners) {
|
||||
listeners.forEach((callback) => {
|
||||
try {
|
||||
callback({
|
||||
type: "danmu",
|
||||
payload: data.data,
|
||||
payload: data,
|
||||
});
|
||||
} catch (e) {
|
||||
console.error(`[Socket.IO] Event listener error for danmu:`, e);
|
||||
@@ -249,6 +260,7 @@ if (!TAURI_ENV) {
|
||||
}
|
||||
|
||||
async function listen<T>(event: string, callback: (data: any) => void) {
|
||||
log.info("listen to event:", event);
|
||||
if (TAURI_ENV) {
|
||||
return await tauri_listen(event, callback);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user