diff --git a/README.md b/README.md
index 65ec366..8a8b460 100644
--- a/README.md
+++ b/README.md
@@ -29,4 +29,4 @@ BiliBili ShadowReplay 是一个缓存直播并进行实时编辑投稿的工具
## 赞助
-
+
diff --git a/src-tauri/capabilities/migrated.json b/src-tauri/capabilities/migrated.json
index 35e13ed..5142d26 100644
--- a/src-tauri/capabilities/migrated.json
+++ b/src-tauri/capabilities/migrated.json
@@ -52,6 +52,9 @@
},
{
"url": "http://tauri.localhost/*"
+ },
+ {
+ "url": "http://localhost:8054/*"
}
]
},
diff --git a/src-tauri/gen/schemas/capabilities.json b/src-tauri/gen/schemas/capabilities.json
index 7f371d1..a491c4e 100644
--- a/src-tauri/gen/schemas/capabilities.json
+++ b/src-tauri/gen/schemas/capabilities.json
@@ -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"]}}
\ No newline at end of file
+{"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"]}}
\ No newline at end of file
diff --git a/src-tauri/src/http_server/websocket.rs b/src-tauri/src/http_server/websocket.rs
index 39faee9..fcf71df 100644
--- a/src-tauri/src/http_server/websocket.rs
+++ b/src-tauri/src/http_server/websocket.rs
@@ -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
- }
}),
),
};
diff --git a/src-tauri/src/progress/progress_reporter.rs b/src-tauri/src/progress/progress_reporter.rs
index 8a39e46..8f6403a 100644
--- a/src-tauri/src/progress/progress_reporter.rs
+++ b/src-tauri/src/progress/progress_reporter.rs
@@ -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,
diff --git a/src/AppLive.svelte b/src/AppLive.svelte
index 915fd71..1b03dec 100644
--- a/src/AppLive.svelte
+++ b/src/AppLive.svelte
@@ -173,30 +173,7 @@
}
}
- const update_listener = listen(`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(
- `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,
diff --git a/src/lib/components/ImportVideoDialog.svelte b/src/lib/components/ImportVideoDialog.svelte
index f3c44e1..09b38f1 100644
--- a/src/lib/components/ImportVideoDialog.svelte
+++ b/src/lib/components/ImportVideoDialog.svelte
@@ -55,47 +55,6 @@
return size.toFixed(decimals) + " " + units[unitIndex];
}
- // 进度监听器
- const progressUpdateListener = listen(
- "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(
- "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,
diff --git a/src/lib/components/Player.svelte b/src/lib/components/Player.svelte
index 5d1c1c5..ab7e41b 100644
--- a/src/lib/components/Player.svelte
+++ b/src/lib/components/Player.svelte
@@ -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;
diff --git a/src/lib/components/VideoPreview.svelte b/src/lib/components/VideoPreview.svelte
index 7817920..70226c7 100644
--- a/src/lib/components/VideoPreview.svelte
+++ b/src/lib/components/VideoPreview.svelte
@@ -178,63 +178,7 @@
}
});
- const update_listener = listen(`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(`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,
diff --git a/src/lib/invoker.ts b/src/lib/invoker.ts
index e7e82a6..f21da6f 100644
--- a/src/lib/invoker.ts
+++ b/src/lib/invoker.ts
@@ -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(event: string, callback: (data: any) => void) {
+ log.info("listen to event:", event);
if (TAURI_ENV) {
return await tauri_listen(event, callback);
}