mirror of
https://github.com/Xinrea/bili-shadowreplay.git
synced 2025-11-24 20:15:34 +08:00
@@ -11,6 +11,11 @@ pub async fn get_config(state: state_type!()) -> Result<Config, ()> {
|
||||
Ok(state.config.read().await.clone())
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "gui", tauri::command)]
|
||||
pub async fn get_static_port(state: state_type!()) -> Result<u16, ()> {
|
||||
Ok(state.static_server.port)
|
||||
}
|
||||
|
||||
#[cfg_attr(feature = "gui", tauri::command)]
|
||||
#[allow(dead_code)]
|
||||
pub async fn set_cache_path(state: state_type!(), cache_path: String) -> Result<(), String> {
|
||||
|
||||
@@ -14,11 +14,11 @@ use crate::{
|
||||
add_account, get_account_count, get_accounts, get_qr, get_qr_status, remove_account,
|
||||
},
|
||||
config::{
|
||||
get_config, update_auto_generate, update_clip_name_format, update_danmu_ass_options,
|
||||
update_notify, update_openai_api_endpoint, update_openai_api_key,
|
||||
update_status_check_interval, update_subtitle_generator_type, update_subtitle_setting,
|
||||
update_webhook_url, update_whisper_language, update_whisper_model,
|
||||
update_whisper_prompt,
|
||||
get_config, get_static_port, update_auto_generate, update_clip_name_format,
|
||||
update_danmu_ass_options, update_notify, update_openai_api_endpoint,
|
||||
update_openai_api_key, update_status_check_interval, update_subtitle_generator_type,
|
||||
update_subtitle_setting, update_webhook_url, update_whisper_language,
|
||||
update_whisper_model, update_whisper_prompt,
|
||||
},
|
||||
message::{delete_message, get_messages, read_message},
|
||||
recorder::{
|
||||
@@ -194,6 +194,15 @@ async fn handler_get_config(
|
||||
Ok(Json(ApiResponse::success(config)))
|
||||
}
|
||||
|
||||
async fn handler_get_static_port(
|
||||
state: axum::extract::State<State>,
|
||||
) -> Result<Json<ApiResponse<u16>>, ApiError> {
|
||||
let static_port = get_static_port(state.0)
|
||||
.await
|
||||
.expect("Failed to get static port");
|
||||
Ok(Json(ApiResponse::success(static_port)))
|
||||
}
|
||||
|
||||
#[derive(Debug, Serialize, Deserialize)]
|
||||
#[serde(rename_all = "camelCase")]
|
||||
struct UpdateStatusCheckIntervalRequest {
|
||||
@@ -1772,12 +1781,10 @@ pub async fn start_api_server(state: State) {
|
||||
log::info!("Running in readonly mode, some api routes are disabled");
|
||||
}
|
||||
|
||||
let cache_path = state.config.read().await.cache.clone();
|
||||
let output_path = state.config.read().await.output.clone();
|
||||
|
||||
app = app
|
||||
// Config commands
|
||||
.route("/api/get_config", post(handler_get_config))
|
||||
.route("/api/get_static_port", post(handler_get_static_port))
|
||||
// Message commands
|
||||
.route("/api/get_messages", post(handler_get_messages))
|
||||
.route("/api/read_message", post(handler_read_message))
|
||||
@@ -1825,9 +1832,7 @@ pub async fn start_api_server(state: State) {
|
||||
.route("/api/fetch", post(handler_fetch))
|
||||
.route("/api/upload_file", post(handler_upload_file))
|
||||
.route("/api/image/:video_id", get(handler_image_base64))
|
||||
.route("/hls/*uri", get(handler_hls))
|
||||
.nest_service("/output", ServeDir::new(output_path))
|
||||
.nest_service("/cache", ServeDir::new(cache_path));
|
||||
.route("/hls/*uri", get(handler_hls));
|
||||
|
||||
let websocket_layer = websocket::create_websocket_server(state.clone()).await;
|
||||
|
||||
|
||||
@@ -13,6 +13,7 @@ mod migration;
|
||||
mod progress;
|
||||
mod recorder_manager;
|
||||
mod state;
|
||||
mod static_server;
|
||||
mod subtitle_generator;
|
||||
mod task;
|
||||
#[cfg(feature = "gui")]
|
||||
@@ -422,7 +423,7 @@ impl MigrationSource<'static> for MigrationList {
|
||||
async fn setup_server_state(args: Args) -> Result<State, Box<dyn std::error::Error>> {
|
||||
use std::path::PathBuf;
|
||||
|
||||
use crate::task::TaskManager;
|
||||
use crate::{static_server::start_static_server, task::TaskManager};
|
||||
use progress::progress_manager::ProgressManager;
|
||||
use progress::progress_reporter::EventEmitter;
|
||||
|
||||
@@ -480,6 +481,8 @@ async fn setup_server_state(args: Args) -> Result<State, Box<dyn std::error::Err
|
||||
webhook_poster.clone(),
|
||||
));
|
||||
|
||||
let static_server = Arc::new(start_static_server(config.clone()).await?);
|
||||
|
||||
let _ = try_rebuild_archives(&db, config.read().await.cache.clone().into()).await;
|
||||
let _ = try_convert_live_covers(&db, config.read().await.cache.clone().into()).await;
|
||||
let _ = try_convert_clip_covers(&db, config.read().await.output.clone().into()).await;
|
||||
@@ -492,6 +495,7 @@ async fn setup_server_state(args: Args) -> Result<State, Box<dyn std::error::Err
|
||||
webhook_poster,
|
||||
recorder_manager,
|
||||
task_manager,
|
||||
static_server,
|
||||
progress_manager,
|
||||
readonly: args.readonly,
|
||||
})
|
||||
@@ -502,7 +506,7 @@ async fn setup_app_state(app: &tauri::App) -> Result<State, Box<dyn std::error::
|
||||
use platform_dirs::AppDirs;
|
||||
use progress::progress_reporter::EventEmitter;
|
||||
|
||||
use crate::task::TaskManager;
|
||||
use crate::{static_server::start_static_server, task::TaskManager};
|
||||
|
||||
let log_dir = app.path().app_log_dir()?;
|
||||
setup_logging(&log_dir).await?;
|
||||
@@ -550,6 +554,8 @@ async fn setup_app_state(app: &tauri::App) -> Result<State, Box<dyn std::error::
|
||||
webhook_poster.clone(),
|
||||
));
|
||||
|
||||
let static_server = Arc::new(start_static_server(config.clone()).await?);
|
||||
|
||||
// try to rebuild archive table
|
||||
let cache_path = config_clone.read().await.cache.clone();
|
||||
let output_path = config_clone.read().await.output.clone();
|
||||
@@ -566,6 +572,7 @@ async fn setup_app_state(app: &tauri::App) -> Result<State, Box<dyn std::error::
|
||||
config,
|
||||
recorder_manager,
|
||||
task_manager,
|
||||
static_server,
|
||||
app_handle: app.handle().clone(),
|
||||
webhook_poster,
|
||||
})
|
||||
@@ -623,6 +630,7 @@ fn setup_invoke_handlers(builder: tauri::Builder<tauri::Wry>) -> tauri::Builder<
|
||||
crate::handlers::account::get_qr_status,
|
||||
crate::handlers::account::get_qr,
|
||||
crate::handlers::config::get_config,
|
||||
crate::handlers::config::get_static_port,
|
||||
crate::handlers::config::set_cache_path,
|
||||
crate::handlers::config::set_output_path,
|
||||
crate::handlers::config::update_notify,
|
||||
|
||||
@@ -4,6 +4,7 @@ use tokio::sync::RwLock;
|
||||
use crate::config::Config;
|
||||
use crate::database::Database;
|
||||
use crate::recorder_manager::RecorderManager;
|
||||
use crate::static_server::StaticServer;
|
||||
use crate::task::TaskManager;
|
||||
use crate::webhook::poster::WebhookPoster;
|
||||
|
||||
@@ -17,6 +18,7 @@ pub struct State {
|
||||
pub webhook_poster: WebhookPoster,
|
||||
pub recorder_manager: Arc<RecorderManager>,
|
||||
pub task_manager: Arc<TaskManager>,
|
||||
pub static_server: Arc<StaticServer>,
|
||||
#[cfg(not(feature = "headless"))]
|
||||
pub app_handle: tauri::AppHandle,
|
||||
#[cfg(feature = "headless")]
|
||||
|
||||
57
src-tauri/src/static_server/mod.rs
Normal file
57
src-tauri/src/static_server/mod.rs
Normal file
@@ -0,0 +1,57 @@
|
||||
use crate::config::Config;
|
||||
use axum::Router;
|
||||
use std::{net::SocketAddr, sync::Arc};
|
||||
use tokio::sync::RwLock;
|
||||
use tokio::task::JoinHandle;
|
||||
use tower_http::cors::{Any, CorsLayer};
|
||||
use tower_http::services::ServeDir;
|
||||
|
||||
pub struct StaticServer {
|
||||
#[allow(dead_code)]
|
||||
pub handle: JoinHandle<()>,
|
||||
pub port: u16,
|
||||
}
|
||||
|
||||
pub async fn start_static_server(
|
||||
config: Arc<RwLock<Config>>,
|
||||
) -> Result<StaticServer, Box<dyn std::error::Error>> {
|
||||
let bind_addr = SocketAddr::from(([0, 0, 0, 0], 0));
|
||||
log::info!("Starting static server binding to {}", bind_addr);
|
||||
|
||||
let listener = match tokio::net::TcpListener::bind(bind_addr).await {
|
||||
Ok(listener) => {
|
||||
match listener.local_addr() {
|
||||
Ok(addr) => log::info!("Static server listening on http://{}", addr),
|
||||
Err(e) => log::warn!("Unable to determine listening address: {}", e),
|
||||
}
|
||||
listener
|
||||
}
|
||||
Err(e) => {
|
||||
log::error!("Failed to bind static server: {}", e);
|
||||
log::error!("Please check if the port is already in use or try a different port");
|
||||
return Err(e.into());
|
||||
}
|
||||
};
|
||||
|
||||
let port = listener.local_addr().unwrap().port();
|
||||
|
||||
let output_path = config.read().await.output.clone();
|
||||
let cache_path = config.read().await.cache.clone();
|
||||
|
||||
let handle = tokio::spawn(async move {
|
||||
let cors = CorsLayer::new()
|
||||
.allow_origin(Any)
|
||||
.allow_methods(Any)
|
||||
.allow_headers(Any);
|
||||
let router = Router::new()
|
||||
.layer(cors)
|
||||
.nest_service("/output", ServeDir::new(output_path))
|
||||
.nest_service("/cache", ServeDir::new(cache_path));
|
||||
|
||||
if let Err(e) = axum::serve(listener, router).await {
|
||||
log::error!("Server error: {}", e);
|
||||
}
|
||||
});
|
||||
|
||||
Ok(StaticServer { handle, port })
|
||||
}
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, convertFileSrc, get_cover } from "./lib/invoker";
|
||||
import { invoke, get_static_url } from "./lib/invoker";
|
||||
import { onMount } from "svelte";
|
||||
import VideoPreview from "./lib/components/VideoPreview.svelte";
|
||||
import type { Config, VideoItem } from "./lib/interface";
|
||||
@@ -36,7 +36,7 @@
|
||||
id: v.id,
|
||||
value: v.id,
|
||||
name: v.file,
|
||||
file: await convertFileSrc(v.file),
|
||||
file: await get_static_url("output", v.file),
|
||||
cover: v.cover,
|
||||
};
|
||||
})
|
||||
@@ -59,7 +59,7 @@
|
||||
async function handleVideoChange(newVideo: VideoItem) {
|
||||
if (newVideo) {
|
||||
if (newVideo.cover && newVideo.cover.trim() !== "") {
|
||||
newVideo.cover = await get_cover("output", newVideo.cover);
|
||||
newVideo.cover = await get_static_url("output", newVideo.cover);
|
||||
} else {
|
||||
newVideo.cover = "";
|
||||
}
|
||||
@@ -76,7 +76,7 @@
|
||||
id: v.id,
|
||||
value: v.id,
|
||||
name: v.file,
|
||||
file: await convertFileSrc(v.file),
|
||||
file: await get_static_url("output", v.file),
|
||||
cover: v.cover,
|
||||
};
|
||||
})
|
||||
|
||||
@@ -3,10 +3,9 @@
|
||||
invoke,
|
||||
set_title,
|
||||
TAURI_ENV,
|
||||
convertFileSrc,
|
||||
listen,
|
||||
log,
|
||||
get_cover,
|
||||
get_static_url,
|
||||
} from "./lib/invoker";
|
||||
import Player from "./lib/components/Player.svelte";
|
||||
import type { RecordItem } from "./lib/db";
|
||||
@@ -15,8 +14,6 @@
|
||||
type VideoItem,
|
||||
type Config,
|
||||
type Marker,
|
||||
type ProgressUpdate,
|
||||
type ProgressFinished,
|
||||
type DanmuEntry,
|
||||
clipRange,
|
||||
generateEventId,
|
||||
@@ -264,7 +261,7 @@
|
||||
id: v.id,
|
||||
value: v.id,
|
||||
name: v.file,
|
||||
file: await convertFileSrc(v.file),
|
||||
file: await get_static_url("output", v.file),
|
||||
cover: v.cover,
|
||||
};
|
||||
})
|
||||
@@ -281,7 +278,7 @@
|
||||
return v.value == id;
|
||||
});
|
||||
if (target_video) {
|
||||
target_video.cover = await get_cover("output", target_video.cover);
|
||||
target_video.cover = await get_static_url("output", target_video.cover);
|
||||
}
|
||||
selected_video = target_video;
|
||||
}
|
||||
@@ -342,7 +339,7 @@
|
||||
fix_encoding,
|
||||
})) as VideoItem;
|
||||
await get_video_list();
|
||||
new_video.cover = await get_cover("output", new_video.cover);
|
||||
new_video.cover = await get_static_url("output", new_video.cover);
|
||||
video_selected = new_video.id;
|
||||
selected_video = videos.find((v) => {
|
||||
return v.value == new_video.id;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, get_cover } from "../invoker";
|
||||
import { invoke, get_static_url } from "../invoker";
|
||||
import type { RecordItem } from "../db";
|
||||
import { fade, scale } from "svelte/transition";
|
||||
import { X, FileVideo } from "lucide-svelte";
|
||||
@@ -34,7 +34,7 @@
|
||||
|
||||
// 处理封面
|
||||
for (const archive of sameParentArchives) {
|
||||
archive.cover = await get_cover(
|
||||
archive.cover = await get_static_url(
|
||||
"cache",
|
||||
`${archive.platform}/${archive.room_id}/${archive.live_id}/cover.jpg`
|
||||
);
|
||||
|
||||
@@ -34,7 +34,7 @@
|
||||
listen,
|
||||
log,
|
||||
close_window,
|
||||
get_cover,
|
||||
get_static_url,
|
||||
} from "../invoker";
|
||||
import { onDestroy, onMount } from "svelte";
|
||||
import { listen as tauriListen } from "@tauri-apps/api/event";
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
import { invoke as tauri_invoke } from "@tauri-apps/api/core";
|
||||
import { getCurrentWebviewWindow } from "@tauri-apps/api/webviewWindow";
|
||||
import { fetch as tauri_fetch } from "@tauri-apps/plugin-http";
|
||||
import { convertFileSrc as tauri_convert } from "@tauri-apps/api/core";
|
||||
import { listen as tauri_listen } from "@tauri-apps/api/event";
|
||||
import { open as tauri_open } from "@tauri-apps/plugin-shell";
|
||||
import { onOpenUrl as tauri_onOpenUrl } from "@tauri-apps/plugin-deep-link";
|
||||
@@ -125,84 +124,23 @@ async function set_title(title: string) {
|
||||
document.title = title;
|
||||
}
|
||||
|
||||
async function convertFileSrc(filePath: string) {
|
||||
if (TAURI_ENV) {
|
||||
// 在客户端模式下,需要获取config来构建绝对路径
|
||||
try {
|
||||
const config = (await invoke("get_config")) as any;
|
||||
const absolutePath = `${config.output}/${filePath}`;
|
||||
return tauri_convert(absolutePath);
|
||||
} catch (error) {
|
||||
console.error("Failed to get config for file path conversion:", error);
|
||||
return tauri_convert(filePath);
|
||||
}
|
||||
}
|
||||
// 在headless模式下,保持完整的相对路径
|
||||
return `${ENDPOINT}/output/${filePath}`;
|
||||
}
|
||||
|
||||
let STATIC_PORT = 0;
|
||||
let config: Config | null = null;
|
||||
const coverCache: Map<string, string> = new Map();
|
||||
|
||||
async function get_cover(coverType: string, coverPath: string) {
|
||||
async function get_static_url(base: string, path: string) {
|
||||
if (config === null) {
|
||||
config = (await invoke("get_config")) as any;
|
||||
}
|
||||
if (TAURI_ENV) {
|
||||
if (coverType === "cache") {
|
||||
const absolutePath = `${config.cache}/${coverPath}`;
|
||||
if (coverCache.has(absolutePath)) {
|
||||
return coverCache.get(absolutePath);
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
try {
|
||||
const exists = (await invoke("file_exists", {
|
||||
path: absolutePath,
|
||||
})) as boolean;
|
||||
if (!exists) {
|
||||
log.error("Cover file not found:", absolutePath);
|
||||
return "/imgs/bilibili.png"; // 返回默认封面
|
||||
}
|
||||
|
||||
const url = tauri_convert(absolutePath);
|
||||
coverCache.set(absolutePath, url);
|
||||
return url;
|
||||
} catch (e) {
|
||||
log.error("Failed to check cover file existence:", e);
|
||||
return "/imgs/bilibili.png"; // 返回默认封面
|
||||
}
|
||||
}
|
||||
|
||||
if (coverType === "output") {
|
||||
const absolutePath = `${config.output}/${coverPath}`;
|
||||
if (coverCache.has(absolutePath)) {
|
||||
return coverCache.get(absolutePath);
|
||||
}
|
||||
|
||||
// 检查文件是否存在
|
||||
try {
|
||||
const exists = (await invoke("file_exists", {
|
||||
path: absolutePath,
|
||||
})) as boolean;
|
||||
if (!exists) {
|
||||
return "/imgs/bilibili.png"; // 返回默认封面
|
||||
}
|
||||
|
||||
const url = tauri_convert(absolutePath);
|
||||
coverCache.set(absolutePath, url);
|
||||
return url;
|
||||
} catch (e) {
|
||||
log.error("Failed to check cover file existence:", e);
|
||||
return "/imgs/bilibili.png"; // 返回默认封面
|
||||
}
|
||||
}
|
||||
|
||||
// exception
|
||||
throw new Error(`Invalid cover type: ${coverType}`);
|
||||
if (STATIC_PORT === 0) {
|
||||
STATIC_PORT = await invoke("get_static_port");
|
||||
}
|
||||
let staticUrl = `http://localhost:${STATIC_PORT}`;
|
||||
if (!TAURI_ENV) {
|
||||
// replace port in ENDPOINT
|
||||
staticUrl = ENDPOINT.replace(/:\d+/, `:${STATIC_PORT}`);
|
||||
}
|
||||
|
||||
return `${ENDPOINT}/${coverType}/${coverPath}`;
|
||||
return `${staticUrl}/${base}/${path}`;
|
||||
}
|
||||
|
||||
let socket: Socket | null = null;
|
||||
@@ -345,12 +283,11 @@ export {
|
||||
get,
|
||||
set_title,
|
||||
TAURI_ENV,
|
||||
convertFileSrc,
|
||||
ENDPOINT,
|
||||
listen,
|
||||
open,
|
||||
log,
|
||||
close_window,
|
||||
onOpenUrl,
|
||||
get_cover,
|
||||
get_static_url,
|
||||
};
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
accounts: [],
|
||||
};
|
||||
|
||||
let avatar_cache: Map<string, string> = new Map();
|
||||
|
||||
async function update_accounts() {
|
||||
let new_account_info = (await invoke("get_accounts")) as AccountInfo;
|
||||
for (const account of new_account_info.accounts) {
|
||||
@@ -17,9 +19,15 @@
|
||||
account.avatar = platform_avatar(account.platform);
|
||||
continue;
|
||||
}
|
||||
if (avatar_cache.has(account.avatar)) {
|
||||
account.avatar = avatar_cache.get(account.avatar);
|
||||
continue;
|
||||
}
|
||||
const avatar_response = await get(account.avatar);
|
||||
const avatar_blob = await avatar_response.blob();
|
||||
account.avatar = URL.createObjectURL(avatar_blob);
|
||||
const avatar_url = URL.createObjectURL(avatar_blob);
|
||||
avatar_cache.set(account.avatar, avatar_url);
|
||||
account.avatar = avatar_url;
|
||||
}
|
||||
account_info = new_account_info;
|
||||
}
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, get_cover } from "../lib/invoker";
|
||||
import { invoke, get_static_url } from "../lib/invoker";
|
||||
import type { RecordItem } from "../lib/db";
|
||||
import { onMount } from "svelte";
|
||||
import {
|
||||
@@ -95,7 +95,7 @@
|
||||
|
||||
// 处理封面
|
||||
for (const archive of roomArchives) {
|
||||
archive.cover = await get_cover(
|
||||
archive.cover = await get_static_url(
|
||||
"cache",
|
||||
`${archive.platform}/${archive.room_id}/${archive.live_id}/cover.jpg`
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, TAURI_ENV, convertFileSrc, get_cover } from "../lib/invoker";
|
||||
import { invoke, TAURI_ENV, get_static_url } from "../lib/invoker";
|
||||
import type { VideoItem } from "../lib/interface";
|
||||
import ImportVideoDialog from "../lib/components/ImportVideoDialog.svelte";
|
||||
import { onMount, onDestroy, tick } from "svelte";
|
||||
@@ -173,7 +173,7 @@
|
||||
const tempVideos = await invoke<VideoItem[]>("get_all_videos");
|
||||
|
||||
for (const video of tempVideos) {
|
||||
video.cover = await get_cover("output", video.cover);
|
||||
video.cover = await get_static_url("output", video.cover);
|
||||
}
|
||||
|
||||
for (const video of tempVideos) {
|
||||
@@ -298,7 +298,7 @@
|
||||
}
|
||||
}
|
||||
|
||||
function getRoomUrl(platform: string | undefined, roomId: number) {
|
||||
function getRoomUrl(platform: string | undefined, roomId: string) {
|
||||
if (!platform) return null;
|
||||
switch (platform.toLowerCase()) {
|
||||
case "bilibili":
|
||||
@@ -393,7 +393,7 @@
|
||||
|
||||
async function exportVideo(video: VideoItem) {
|
||||
// download video
|
||||
const video_url = await convertFileSrc(video.file);
|
||||
const video_url = await get_static_url("output", video.file);
|
||||
const video_name = video.title;
|
||||
const a = document.createElement("a");
|
||||
a.href = video_url;
|
||||
@@ -428,7 +428,7 @@
|
||||
|
||||
// 更新筛选后的视频列表
|
||||
const index = filteredVideos.findIndex(
|
||||
(v) => v.id === videoToEditNote.id,
|
||||
(v) => v.id === videoToEditNote.id
|
||||
);
|
||||
if (index !== -1) {
|
||||
filteredVideos[index].note = editingNote;
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { invoke, open, onOpenUrl, get_cover, get } from "../lib/invoker";
|
||||
import { invoke, open, onOpenUrl, get_static_url, get } from "../lib/invoker";
|
||||
import { message } from "@tauri-apps/plugin-dialog";
|
||||
import { fade, scale } from "svelte/transition";
|
||||
import { Dropdown, DropdownItem } from "flowbite-svelte";
|
||||
@@ -57,6 +57,32 @@
|
||||
}
|
||||
}
|
||||
|
||||
let avatar_cache: Map<string, string> = new Map();
|
||||
async function get_avatar_url(user_id: string, url: string) {
|
||||
if (avatar_cache.has(user_id)) {
|
||||
return avatar_cache.get(user_id);
|
||||
}
|
||||
console.log("get avatar url:", url);
|
||||
const response = await get(url);
|
||||
const blob = await response.blob();
|
||||
const avatar_url = URL.createObjectURL(blob);
|
||||
avatar_cache.set(user_id, avatar_url);
|
||||
return avatar_url;
|
||||
}
|
||||
|
||||
let image_cache: Map<string, string> = new Map();
|
||||
async function get_image_url(url: string) {
|
||||
if (image_cache.has(url)) {
|
||||
return image_cache.get(url);
|
||||
}
|
||||
console.log("get image url:", url);
|
||||
const response = await get(url);
|
||||
const blob = await response.blob();
|
||||
const cover_url = URL.createObjectURL(blob);
|
||||
image_cache.set(url, cover_url);
|
||||
return cover_url;
|
||||
}
|
||||
|
||||
async function update_summary() {
|
||||
let new_summary = (await invoke("get_recorder_list")) as RecorderList;
|
||||
room_count = new_summary.count;
|
||||
@@ -77,17 +103,18 @@
|
||||
// process room cover
|
||||
for (const room of new_summary.recorders) {
|
||||
if (room.room_info.room_cover != "") {
|
||||
const cover_response = await get(room.room_info.room_cover);
|
||||
const cover_blob = await cover_response.blob();
|
||||
room.room_info.room_cover = URL.createObjectURL(cover_blob);
|
||||
room.room_info.room_cover = await get_image_url(
|
||||
room.room_info.room_cover
|
||||
);
|
||||
} else {
|
||||
room.room_info.room_cover = default_cover(room.room_info.platform);
|
||||
}
|
||||
|
||||
if (room.user_info.user_avatar != "") {
|
||||
const avatar_response = await get(room.user_info.user_avatar);
|
||||
const avatar_blob = await avatar_response.blob();
|
||||
room.user_info.user_avatar = URL.createObjectURL(avatar_blob);
|
||||
room.user_info.user_avatar = await get_avatar_url(
|
||||
room.user_info.user_id,
|
||||
room.user_info.user_avatar
|
||||
);
|
||||
} else {
|
||||
room.user_info.user_avatar = default_avatar(room.room_info.platform);
|
||||
}
|
||||
@@ -145,7 +172,7 @@
|
||||
})) as RecordItem[];
|
||||
|
||||
for (const archive of new_archives) {
|
||||
archive.cover = await get_cover(
|
||||
archive.cover = await get_static_url(
|
||||
"cache",
|
||||
`${archive.platform}/${archive.room_id}/${archive.live_id}/cover.jpg`
|
||||
);
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<script lang="ts">
|
||||
import { get_cover, invoke } from "../lib/invoker";
|
||||
import { get_static_url, invoke } from "../lib/invoker";
|
||||
import type { RecorderList, DiskInfo } from "../lib/interface";
|
||||
import type { RecordItem } from "../lib/db";
|
||||
const INTERVAL = 5000;
|
||||
@@ -90,7 +90,7 @@
|
||||
})) as RecordItem[];
|
||||
|
||||
for (const record of newRecords) {
|
||||
record.cover = await get_cover(
|
||||
record.cover = await get_static_url(
|
||||
"cache",
|
||||
`${record.platform}/${record.room_id}/${record.live_id}/cover.jpg`
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user