mirror of
https://github.com/algerkong/AlgerMusicPlayer.git
synced 2026-07-06 08:47:28 +08:00
fix(local-music): 主进程实现 Range/206 让本地音乐 seek/快进恢复
切到 protocol.handle 后,audio 元素发的 Range 请求会带 HTTP 头到达 主进程;net.fetch(file://) 既不识别 Range 也不带 Accept-Ranges,导致 浏览器认为 local:// 不支持随机访问,seek 时只能从头加载,表现为 快进失效、退出后恢复播放从 0 开始。 新增 buildLocalFileResponse: - 无 Range:200 + Accept-Ranges: bytes,让 audio 知道支持随机访问 - 有 Range:206 + Content-Range,用 fs.createReadStream 切片返回 - 非法 Range:416 + Content-Range: bytes */total 顺手把 fs.existsSync 换成 stat,一次拿文件大小且过滤掉目录。
This commit is contained in:
@@ -1,8 +1,8 @@
|
||||
import { app, dialog, ipcMain, net, protocol, shell } from 'electron';
|
||||
import { app, dialog, ipcMain, protocol, shell } from 'electron';
|
||||
import Store from 'electron-store';
|
||||
import * as fs from 'fs';
|
||||
import * as path from 'path';
|
||||
import { pathToFileURL } from 'url';
|
||||
import { Readable } from 'stream';
|
||||
|
||||
import { getStore } from './config';
|
||||
|
||||
@@ -24,6 +24,45 @@ function sanitizeFilename(filename: string): string {
|
||||
.trim();
|
||||
}
|
||||
|
||||
// Electron net.fetch(file://) 在当前版本不会回 206,audio seek 需要主进程自己处理 Range
|
||||
function buildLocalFileResponse(
|
||||
filePath: string,
|
||||
total: number,
|
||||
rangeHeader: string | null
|
||||
): Response {
|
||||
const range416 = () =>
|
||||
new Response(null, { status: 416, headers: { 'Content-Range': `bytes */${total}` } });
|
||||
|
||||
let start = 0;
|
||||
let end = total - 1;
|
||||
let partial = false;
|
||||
|
||||
if (rangeHeader) {
|
||||
const m = /^bytes=(\d*)-(\d*)$/.exec(rangeHeader.trim());
|
||||
if (!m || (!m[1] && !m[2])) return range416();
|
||||
if (m[1]) {
|
||||
start = parseInt(m[1], 10);
|
||||
if (m[2]) end = Math.min(parseInt(m[2], 10), end);
|
||||
} else {
|
||||
start = Math.max(0, total - parseInt(m[2], 10));
|
||||
}
|
||||
if (start > end || start >= total) return range416();
|
||||
partial = true;
|
||||
}
|
||||
|
||||
return new Response(
|
||||
Readable.toWeb(fs.createReadStream(filePath, { start, end })) as ReadableStream,
|
||||
{
|
||||
status: partial ? 206 : 200,
|
||||
headers: {
|
||||
'Content-Length': String(end - start + 1),
|
||||
'Accept-Ranges': 'bytes',
|
||||
...(partial && { 'Content-Range': `bytes ${start}-${end}/${total}` })
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* 初始化文件管理相关的IPC监听
|
||||
*/
|
||||
@@ -48,13 +87,13 @@ export function initializeFileManager() {
|
||||
|
||||
filePath = path.normalize(filePath);
|
||||
|
||||
if (!fs.existsSync(filePath)) {
|
||||
const stat = await fs.promises.stat(filePath).catch(() => null);
|
||||
if (!stat?.isFile()) {
|
||||
console.error('File not found:', filePath);
|
||||
return new Response(null, { status: 404 });
|
||||
}
|
||||
|
||||
// 用 net.fetch 走 file:// 加载,自动支持 Range / streaming,让媒体元素能正常 seek
|
||||
return net.fetch(pathToFileURL(filePath).toString());
|
||||
return buildLocalFileResponse(filePath, stat.size, request.headers.get('range'));
|
||||
} catch (error) {
|
||||
console.error('Error handling local protocol:', error);
|
||||
return new Response(null, { status: 500 });
|
||||
|
||||
Reference in New Issue
Block a user