fix: progress and danmu events

This commit is contained in:
Xinrea
2025-09-28 01:54:37 +08:00
parent ac806b49b2
commit 7b7f341fa0
10 changed files with 221 additions and 155 deletions

View File

@@ -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">

View File

@@ -52,6 +52,9 @@
},
{
"url": "http://tauri.localhost/*"
},
{
"url": "http://localhost:8054/*"
}
]
},

View File

@@ -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"]}}

View File

@@ -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
}
}),
),
};

View File

@@ -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,

View File

@@ -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,

View File

@@ -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,

View File

@@ -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;

View File

@@ -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,

View File

@@ -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);
}