diff --git a/src/main/index.ts b/src/main/index.ts index 40190df..150ebc6 100644 --- a/src/main/index.ts +++ b/src/main/index.ts @@ -1,7 +1,24 @@ import { electronApp, optimizer } from '@electron-toolkit/utils'; -import { app, ipcMain, nativeImage, session } from 'electron'; +import { app, ipcMain, nativeImage, protocol, session } from 'electron'; import { join } from 'path'; +// 必须在 app.whenReady() 之前注册自定义协议为特权协议, +// 否则 http(s) 页面(dev server、生产环境的 file://)无法把 local:// 当成 +// 安全/可 fetch/可流式的资源加载,会触发 CORS 拦截或 net::ERR_UNKNOWN_URL_SCHEME +protocol.registerSchemesAsPrivileged([ + { + scheme: 'local', + privileges: { + standard: true, + secure: true, + supportFetchAPI: true, + stream: true, + bypassCSP: true, + corsEnabled: true + } + } +]); + import type { Language } from '../i18n/main'; import i18n from '../i18n/main'; import { loadLyricWindow } from './lyric'; diff --git a/src/main/modules/fileManager.ts b/src/main/modules/fileManager.ts index ce6e823..45ae3ef 100644 --- a/src/main/modules/fileManager.ts +++ b/src/main/modules/fileManager.ts @@ -1,7 +1,8 @@ -import { app, dialog, ipcMain, protocol, shell } from 'electron'; +import { app, dialog, ipcMain, net, protocol, shell } from 'electron'; import Store from 'electron-store'; import * as fs from 'fs'; import * as path from 'path'; +import { pathToFileURL } from 'url'; import { getStore } from './config'; @@ -28,31 +29,35 @@ function sanitizeFilename(filename: string): string { */ export function initializeFileManager() { // 注册本地文件协议 - protocol.registerFileProtocol('local', (request, callback) => { + // Electron 25+ 起 registerFileProtocol 已弃用,改用 protocol.handle,并配合 main/index.ts + // 中的 registerSchemesAsPrivileged,让 audio 元素能从 http(s) 页面跨协议加载本地文件 + protocol.handle('local', async (request) => { try { - const url = request.url; - // local://C:/Users/xxx.mp3 - let filePath = decodeURIComponent(url.replace('local:///', '')); + // local:/// + let filePath = decodeURIComponent(request.url.replace(/^local:\/\/\/?/, '')); - // 兼容 local:///C:/Users/xxx.mp3 这种情况 + // Windows: 协议解析后可能是 /C:/...,去掉前导斜杠 if (/^\/[a-zA-Z]:\//.test(filePath)) { filePath = filePath.slice(1); } - // 还原为系统路径格式 - filePath = path.normalize(filePath); - - // 检查文件是否存在 - if (!fs.existsSync(filePath)) { - console.error('File not found:', filePath); - callback({ error: -6 }); // net::ERR_FILE_NOT_FOUND - return; + // macOS/Linux 上去掉前导斜杠后会丢失绝对路径标识,这里补回 + if (process.platform !== 'win32' && !filePath.startsWith('/')) { + filePath = '/' + filePath; } - callback({ path: filePath }); + filePath = path.normalize(filePath); + + if (!fs.existsSync(filePath)) { + console.error('File not found:', filePath); + return new Response(null, { status: 404 }); + } + + // 用 net.fetch 走 file:// 加载,自动支持 Range / streaming,让媒体元素能正常 seek + return net.fetch(pathToFileURL(filePath).toString()); } catch (error) { console.error('Error handling local protocol:', error); - callback({ error: -2 }); // net::FAILED + return new Response(null, { status: 500 }); } });