mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-08-08 07:21:31 -07:00
fix(anime): strip disguised HLS segments and report real playback errors
Some hosts prepend a fake image header (a real 1x1 PNG) to every HLS segment; ffmpeg probes the segment as a picture and mpv drops back to idle with no window while the browser claims the episode is playing. - Route bridge-served m3u8 streams through a local strip proxy that scans each segment for the first genuine MPEG-TS packet run and drops the junk in front of it; playlists get absolute origins rewritten so segment requests come back through the proxy. Non-TS bodies pass through untouched. - Only report ok from playEpisode once mpv configures a video output; an end-file with reason error surfaces mpv's own message (e.g. "no audio or video data played") in the browser status bar instead of "Playing". New end-file event plumbed through the mpv IPC client.
This commit is contained in:
@@ -351,6 +351,28 @@ test('visibility and boolean parsers handle text values', () => {
|
||||
assert.equal(asBoolean('0', true), false);
|
||||
});
|
||||
|
||||
test('dispatchMpvProtocolMessage emits end-file with reason and file error', async () => {
|
||||
const received: Array<{ reason: string; fileError: string | null }> = [];
|
||||
const { deps } = createDeps({
|
||||
emitEndFile: (payload) => {
|
||||
received.push(payload);
|
||||
},
|
||||
});
|
||||
|
||||
await dispatchMpvProtocolMessage(
|
||||
{ event: 'end-file', reason: 'error', file_error: 'no audio or video data played' },
|
||||
deps,
|
||||
);
|
||||
await dispatchMpvProtocolMessage({ event: 'end-file', reason: 'eof' }, deps);
|
||||
await dispatchMpvProtocolMessage({ event: 'end-file' }, deps);
|
||||
|
||||
assert.deepEqual(received, [
|
||||
{ reason: 'error', fileError: 'no audio or video data played' },
|
||||
{ reason: 'eof', fileError: null },
|
||||
{ reason: 'unknown', fileError: null },
|
||||
]);
|
||||
});
|
||||
|
||||
test('dispatchMpvProtocolMessage emits client-message string args', async () => {
|
||||
const received: Array<{ args: string[] }> = [];
|
||||
const { deps } = createDeps({
|
||||
|
||||
@@ -7,6 +7,8 @@ export type MpvMessage = {
|
||||
args?: unknown;
|
||||
request_id?: number;
|
||||
error?: string;
|
||||
reason?: unknown;
|
||||
file_error?: unknown;
|
||||
};
|
||||
|
||||
export const MPV_REQUEST_ID_SUBTEXT = 101;
|
||||
@@ -96,6 +98,7 @@ export interface MpvProtocolHandleMessageDeps {
|
||||
shouldQuitOnMpvShutdown: () => boolean;
|
||||
requestAppQuit: () => void;
|
||||
emitClientMessage?: (payload: { args: string[] }) => void;
|
||||
emitEndFile?: (payload: { reason: string; fileError: string | null }) => void;
|
||||
}
|
||||
|
||||
type SubtitleTrackCandidate = {
|
||||
@@ -385,6 +388,12 @@ export async function dispatchMpvProtocolMessage(
|
||||
if (args.length > 0) {
|
||||
deps.emitClientMessage?.({ args });
|
||||
}
|
||||
} else if (msg.event === 'end-file') {
|
||||
deps.emitEndFile?.({
|
||||
reason: typeof msg.reason === 'string' ? msg.reason : 'unknown',
|
||||
fileError:
|
||||
typeof msg.file_error === 'string' && msg.file_error.length > 0 ? msg.file_error : null,
|
||||
});
|
||||
} else if (msg.event === 'shutdown') {
|
||||
deps.restorePreviousSecondarySubVisibility();
|
||||
if (deps.shouldQuitOnMpvShutdown()) {
|
||||
|
||||
@@ -131,6 +131,7 @@ export interface MpvIpcClientEventMap {
|
||||
'subtitle-metrics-change': { patch: Partial<MpvSubtitleRenderMetrics> };
|
||||
'secondary-subtitle-visibility': { visible: boolean };
|
||||
'client-message': { args: string[] };
|
||||
'end-file': { reason: string; fileError: string | null };
|
||||
}
|
||||
|
||||
type MpvIpcClientEventName = keyof MpvIpcClientEventMap;
|
||||
@@ -496,6 +497,9 @@ export class MpvIpcClient implements MpvClient {
|
||||
emitClientMessage: (payload) => {
|
||||
this.emit('client-message', payload);
|
||||
},
|
||||
emitEndFile: (payload) => {
|
||||
this.emit('end-file', payload);
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user