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:
2026-08-01 00:05:31 -07:00
parent 81856539e4
commit 835426d1e9
13 changed files with 767 additions and 23 deletions
+27 -1
View File
@@ -1,8 +1,34 @@
import test from 'node:test';
import assert from 'node:assert/strict';
import { parseAnimeStatus, resolveBridgeMediaUrl } from './media-url';
import { parseAnimeStatus, resolveBridgeMediaUrl, routeHlsThroughProxy } from './media-url';
const BRIDGE = 'http://127.0.0.1:56037';
const PROXY = 'http://127.0.0.1:60001';
test('a bridge m3u8 stream is routed through the strip proxy', () => {
assert.equal(
routeHlsThroughProxy(`${BRIDGE}/video/master.m3u8?q=1080`, BRIDGE, PROXY),
`${PROXY}/video/master.m3u8?q=1080`,
);
});
test('non-HLS bridge streams stay on the bridge', () => {
const direct = `${BRIDGE}/video/movie-token`;
assert.equal(routeHlsThroughProxy(direct, BRIDGE, PROXY), direct);
});
test('external m3u8 streams are not routed through the proxy', () => {
const remote = 'https://cdn.example.com/hls/master.m3u8';
assert.equal(routeHlsThroughProxy(remote, BRIDGE, PROXY), remote);
});
test('unparseable stream urls pass through routeHlsThroughProxy unchanged', () => {
assert.equal(routeHlsThroughProxy('not a url', BRIDGE, PROXY), 'not a url');
assert.equal(
routeHlsThroughProxy(`${BRIDGE}/video/a.m3u8`, 'garbage', PROXY),
`${BRIDGE}/video/a.m3u8`,
);
});
test('a loopback proxy url is rebased onto the live bridge port', () => {
assert.equal(