mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-08 19:21:32 -07:00
fix(subsync): decode file:// tracks mpv reports for dropped subtitles
- Sync a subtitle dropped onto mpv without hitting "Protocol file: not supported"; decode file:// external-filename/path back to a real path for target, reference, and video - Stream strip proxy: destroy the connection instead of retrying/502 once a response is handed off, and cap buffered playlist bodies - Bridge installer: cap downloaded bundle size to guard against a lying/missing content-length - Anime browser playback: bump generation on dispose so a stale in-flight playEpisode cleans up its own subtitle cache - Fix a flaky sidecar-process test by binding to an OS-assigned port instead of pre-allocating one
This commit is contained in:
@@ -2,6 +2,7 @@ import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import http from 'node:http';
|
||||
import { EventEmitter } from 'node:events';
|
||||
import type { AddressInfo } from 'node:net';
|
||||
import type { spawn as spawnType, ChildProcess } from 'node:child_process';
|
||||
import { allocatePort, startSidecar } from './sidecar-process';
|
||||
import type { BundleBinaries } from './sidecar-bundle';
|
||||
@@ -92,7 +93,6 @@ test('an early exit is reported with its code rather than waiting out the deadli
|
||||
});
|
||||
|
||||
test('onExit reports a death after readiness, including to late subscribers', async () => {
|
||||
const port = await allocatePort();
|
||||
// Fake the bridge's capabilities endpoint so startSidecar reports ready.
|
||||
const server = http.createServer((_req, res) => {
|
||||
res.writeHead(200, { 'content-type': 'application/json' });
|
||||
@@ -100,7 +100,10 @@ test('onExit reports a death after readiness, including to late subscribers', as
|
||||
JSON.stringify({ mangatanMihonBridge: 1, sourceFactory: true, preferenceCallbacks: true }),
|
||||
);
|
||||
});
|
||||
await new Promise<void>((resolve) => server.listen(port, '127.0.0.1', resolve));
|
||||
// Bind first and take the port the OS assigned: allocating one up front and
|
||||
// binding it after leaves a window for another listener to claim it.
|
||||
await new Promise<void>((resolve) => server.listen(0, '127.0.0.1', resolve));
|
||||
const { port } = server.address() as AddressInfo;
|
||||
const child = fakeChild();
|
||||
const spawnImpl = (() => child) as unknown as typeof spawnType;
|
||||
|
||||
|
||||
@@ -149,6 +149,10 @@ export function startStreamStripProxy(
|
||||
attempt: number,
|
||||
): void {
|
||||
const mayRetry = req.method === 'GET' && attempt === 0;
|
||||
// Once the response is handed off, its headers (and often part of its body)
|
||||
// are already on the wire: a later upstream error can only be reported by
|
||||
// killing the connection, never by retrying or writing a 502.
|
||||
let handedOff = false;
|
||||
const retry = (): void => {
|
||||
setTimeout(
|
||||
() => requestUpstream(req, res, upstreamUrl, requestHeaders, attempt + 1),
|
||||
@@ -172,6 +176,7 @@ export function startStreamStripProxy(
|
||||
}
|
||||
log(`[stream-proxy] upstream ${status} for ${upstreamUrl.pathname}`);
|
||||
}
|
||||
handedOff = true;
|
||||
handleUpstreamResponse(req, res, upstream);
|
||||
},
|
||||
);
|
||||
@@ -180,6 +185,11 @@ export function startStreamStripProxy(
|
||||
upstreamRequest.destroy(new Error(`upstream silent for ${UPSTREAM_TIMEOUT_MS}ms`));
|
||||
});
|
||||
upstreamRequest.on('error', (error) => {
|
||||
if (handedOff) {
|
||||
log(`[stream-proxy] upstream failed mid-response: ${String(error)}`);
|
||||
res.destroy();
|
||||
return;
|
||||
}
|
||||
if (mayRetry) {
|
||||
log(`[stream-proxy] upstream request failed: ${String(error)}; retrying once`);
|
||||
retry();
|
||||
@@ -214,7 +224,19 @@ export function startStreamStripProxy(
|
||||
|
||||
if (isPlaylist) {
|
||||
const chunks: Buffer[] = [];
|
||||
upstream.on('data', (chunk: Buffer) => chunks.push(chunk));
|
||||
let buffered = 0;
|
||||
upstream.on('data', (chunk: Buffer) => {
|
||||
buffered += chunk.length;
|
||||
// A playlist is text and small; anything this large is not one, and it
|
||||
// has to be held whole in memory to be rewritten.
|
||||
if (buffered > DECISION_BYTES) {
|
||||
log(`[stream-proxy] playlist body over ${DECISION_BYTES} bytes; dropping`);
|
||||
upstream.destroy();
|
||||
res.destroy();
|
||||
return;
|
||||
}
|
||||
chunks.push(chunk);
|
||||
});
|
||||
upstream.on('end', () => {
|
||||
const body = rewritePlaylistOrigins(
|
||||
Buffer.concat(chunks).toString('utf8'),
|
||||
|
||||
Reference in New Issue
Block a user