Fix Windows YouTube playback flow and overlay pointer tracking

This commit is contained in:
2026-03-25 15:25:17 -07:00
committed by sudacode
parent 5ee4617607
commit c95518e94a
26 changed files with 1044 additions and 36 deletions

View File

@@ -0,0 +1,153 @@
import { isYoutubeMediaPath } from './youtube-playback';
type YoutubePlaybackLaunchInput = {
url: string;
timeoutMs?: number;
pollIntervalMs?: number;
};
type YoutubePlaybackLaunchDeps = {
requestPath: () => Promise<string | null>;
requestProperty?: (name: string) => Promise<unknown>;
sendMpvCommand: (command: Array<string>) => void;
wait: (ms: number) => Promise<void>;
now?: () => number;
};
function normalizePath(value: string | null | undefined): string {
if (typeof value !== 'string') return '';
return value.trim();
}
function extractYoutubeVideoId(url: string): string | null {
let parsed: URL;
try {
parsed = new URL(url);
} catch {
return null;
}
const host = parsed.hostname.toLowerCase();
const path = parsed.pathname.replace(/^\/+/, '');
if (host === 'youtu.be' || host.endsWith('.youtu.be')) {
const id = path.split('/')[0]?.trim() || '';
return id || null;
}
const youtubeHost =
host === 'youtube.com' ||
host.endsWith('.youtube.com') ||
host === 'youtube-nocookie.com' ||
host.endsWith('.youtube-nocookie.com');
if (!youtubeHost) {
return null;
}
if (parsed.pathname === '/watch') {
const id = parsed.searchParams.get('v')?.trim() || '';
return id || null;
}
if (path.startsWith('shorts/') || path.startsWith('embed/')) {
const id = path.split('/')[1]?.trim() || '';
return id || null;
}
return null;
}
function targetsSameYoutubeVideo(currentPath: string, targetUrl: string): boolean {
const currentId = extractYoutubeVideoId(currentPath);
const targetId = extractYoutubeVideoId(targetUrl);
if (!currentId || !targetId) return false;
return currentId === targetId;
}
function pathMatchesYoutubeTarget(currentPath: string, targetUrl: string): boolean {
if (!currentPath) return false;
if (currentPath === targetUrl) return true;
return targetsSameYoutubeVideo(currentPath, targetUrl);
}
function hasPlayableMediaTracks(trackListRaw: unknown): boolean {
if (!Array.isArray(trackListRaw)) return false;
return trackListRaw.some((track) => {
if (!track || typeof track !== 'object') return false;
const type = String((track as Record<string, unknown>).type || '').trim().toLowerCase();
return type === 'video' || type === 'audio';
});
}
export function createPrepareYoutubePlaybackInMpvHandler(deps: YoutubePlaybackLaunchDeps) {
const now = deps.now ?? (() => Date.now());
return async (input: YoutubePlaybackLaunchInput): Promise<boolean> => {
const targetUrl = input.url.trim();
if (!targetUrl) return false;
const timeoutMs = Math.max(200, input.timeoutMs ?? 5000);
const pollIntervalMs = Math.max(25, input.pollIntervalMs ?? 100);
let previousPath = '';
try {
previousPath = normalizePath(await deps.requestPath());
} catch {
// Ignore transient path request failures and continue with bootstrap commands.
}
if (pathMatchesYoutubeTarget(previousPath, targetUrl)) {
return true;
}
deps.sendMpvCommand(['set_property', 'pause', 'yes']);
deps.sendMpvCommand(['set_property', 'sub-auto', 'no']);
deps.sendMpvCommand(['set_property', 'sid', 'no']);
deps.sendMpvCommand(['set_property', 'secondary-sid', 'no']);
deps.sendMpvCommand(['loadfile', targetUrl, 'replace']);
const deadline = now() + timeoutMs;
while (now() < deadline) {
await deps.wait(pollIntervalMs);
let currentPath = '';
try {
currentPath = normalizePath(await deps.requestPath());
} catch {
continue;
}
if (!currentPath) continue;
if (pathMatchesYoutubeTarget(currentPath, targetUrl)) {
if (!deps.requestProperty) {
return true;
}
try {
const trackList = await deps.requestProperty('track-list');
if (hasPlayableMediaTracks(trackList)) {
return true;
}
} catch {
// Continue polling until media tracks are actually available.
}
}
if (previousPath && currentPath !== previousPath) {
if (
isYoutubeMediaPath(currentPath) &&
isYoutubeMediaPath(targetUrl)
) {
return true;
}
if (deps.requestProperty) {
try {
const trackList = await deps.requestProperty('track-list');
if (hasPlayableMediaTracks(trackList)) {
return true;
}
} catch {
// Continue polling until media tracks are actually available.
}
}
}
}
return false;
};
}