feat: add app-owned YouTube subtitle flow with absPlayer-style parsing (#31)

* fix: harden preload argv parsing for popup windows

* fix: align youtube playback with shared overlay startup

* fix: unwrap mpv youtube streams for anki media mining

* docs: update docs for youtube subtitle and mining flow

* refactor: unify cli and runtime wiring for startup and youtube flow

* feat: update subtitle sidebar overlay behavior

* chore: add shared log-file source for diagnostics

* fix(ci): add changelog fragment for immersion changes

* fix: address CodeRabbit review feedback

* fix: persist canonical title from youtube metadata

* style: format stats library tab

* fix: address latest review feedback

* style: format stats library files

* test: stub launcher youtube deps in CI

* test: isolate launcher youtube flow deps

* test: stub launcher youtube deps in failing case

* test: force x11 backend in launcher ci harness

* test: address latest review feedback

* fix(launcher): preserve user YouTube ytdl raw options

* docs(backlog): update task tracking notes

* fix(immersion): special-case youtube media paths in runtime and tracking

* feat(stats): improve YouTube media metadata and picker key handling

* fix(ci): format stats media library hook

* fix: address latest CodeRabbit review items

* docs: update youtube release notes and docs

* feat: auto-load youtube subtitles before manual picker

* fix: restore app-owned youtube subtitle flow

* docs: update youtube playback docs and config copy

* refactor: remove legacy youtube launcher mode plumbing

* fix: refine youtube subtitle startup binding

* docs: clarify youtube subtitle startup behavior

* fix: address PR #31 latest review follow-ups

* fix: address PR #31 follow-up review comments

* test: harden youtube picker test harness

* udpate backlog

* fix: add timeout to youtube metadata probe

* docs: refresh youtube and stats docs

* update backlog

* update backlog

* chore: release v0.9.0
This commit is contained in:
2026-03-24 00:01:24 -07:00
committed by GitHub
parent c17f0a4080
commit 5feed360ca
219 changed files with 12778 additions and 1052 deletions

View File

@@ -37,6 +37,7 @@ import { createSessionHelpModal } from './modals/session-help.js';
import { createSubtitleSidebarModal } from './modals/subtitle-sidebar.js';
import { createRuntimeOptionsModal } from './modals/runtime-options.js';
import { createSubsyncModal } from './modals/subsync.js';
import { createYoutubeTrackPickerModal } from './modals/youtube-track-picker.js';
import { createPositioningController } from './positioning.js';
import { createOverlayContentMeasurementReporter } from './overlay-content-measurement.js';
import { syncOverlayMouseIgnoreState } from './overlay-mouse-ignore.js';
@@ -68,6 +69,7 @@ function isAnySettingsModalOpen(): boolean {
ctx.state.subsyncModalOpen ||
ctx.state.kikuModalOpen ||
ctx.state.jimakuModalOpen ||
ctx.state.youtubePickerModalOpen ||
ctx.state.sessionHelpModalOpen
);
}
@@ -80,6 +82,7 @@ function isAnyModalOpen(): boolean {
ctx.state.kikuModalOpen ||
ctx.state.runtimeOptionsModalOpen ||
ctx.state.subsyncModalOpen ||
ctx.state.youtubePickerModalOpen ||
ctx.state.sessionHelpModalOpen ||
ctx.state.subtitleSidebarModalOpen
);
@@ -128,11 +131,29 @@ const jimakuModal = createJimakuModal(ctx, {
modalStateReader: { isAnyModalOpen },
syncSettingsModalSubtitleSuppression,
});
const mouseHandlers = createMouseHandlers(ctx, {
modalStateReader: { isAnySettingsModalOpen, isAnyModalOpen },
applyYPercent: positioning.applyYPercent,
getCurrentYPercent: positioning.getCurrentYPercent,
persistSubtitlePositionPatch: positioning.persistSubtitlePositionPatch,
getSubtitleHoverAutoPauseEnabled: () => ctx.state.autoPauseVideoOnSubtitleHover,
getYomitanPopupAutoPauseEnabled: () => ctx.state.autoPauseVideoOnYomitanPopup,
getPlaybackPaused: () => window.electronAPI.getPlaybackPaused(),
sendMpvCommand: (command) => {
window.electronAPI.sendMpvCommand(command);
},
});
const youtubePickerModal = createYoutubeTrackPickerModal(ctx, {
modalStateReader: { isAnyModalOpen },
restorePointerInteractionState: mouseHandlers.restorePointerInteractionState,
syncSettingsModalSubtitleSuppression,
});
const keyboardHandlers = createKeyboardHandlers(ctx, {
handleRuntimeOptionsKeydown: runtimeOptionsModal.handleRuntimeOptionsKeydown,
handleSubsyncKeydown: subsyncModal.handleSubsyncKeydown,
handleKikuKeydown: kikuModal.handleKikuKeydown,
handleJimakuKeydown: jimakuModal.handleJimakuKeydown,
handleYoutubePickerKeydown: youtubePickerModal.handleYoutubePickerKeydown,
handleControllerSelectKeydown: controllerSelectModal.handleControllerSelectKeydown,
handleControllerDebugKeydown: controllerDebugModal.handleControllerDebugKeydown,
handleSessionHelpKeydown: sessionHelpModal.handleSessionHelpKeydown,
@@ -153,18 +174,6 @@ const keyboardHandlers = createKeyboardHandlers(ctx, {
void subtitleSidebarModal.toggleSubtitleSidebarModal();
},
});
const mouseHandlers = createMouseHandlers(ctx, {
modalStateReader: { isAnySettingsModalOpen, isAnyModalOpen },
applyYPercent: positioning.applyYPercent,
getCurrentYPercent: positioning.getCurrentYPercent,
persistSubtitlePositionPatch: positioning.persistSubtitlePositionPatch,
getSubtitleHoverAutoPauseEnabled: () => ctx.state.autoPauseVideoOnSubtitleHover,
getYomitanPopupAutoPauseEnabled: () => ctx.state.autoPauseVideoOnYomitanPopup,
getPlaybackPaused: () => window.electronAPI.getPlaybackPaused(),
sendMpvCommand: (command) => {
window.electronAPI.sendMpvCommand(command);
},
});
let lastSubtitlePreview = '';
let lastSecondarySubtitlePreview = '';
@@ -194,6 +203,7 @@ function getActiveModal(): string | null {
if (ctx.state.controllerDebugModalOpen) return 'controller-debug';
if (ctx.state.subtitleSidebarModalOpen) return 'subtitle-sidebar';
if (ctx.state.jimakuModalOpen) return 'jimaku';
if (ctx.state.youtubePickerModalOpen) return 'youtube-track-picker';
if (ctx.state.kikuModalOpen) return 'kiku';
if (ctx.state.runtimeOptionsModalOpen) return 'runtime-options';
if (ctx.state.subsyncModalOpen) return 'subsync';
@@ -214,6 +224,9 @@ function dismissActiveUiAfterError(): void {
if (ctx.state.jimakuModalOpen) {
jimakuModal.closeJimakuModal();
}
if (ctx.state.youtubePickerModalOpen) {
youtubePickerModal.closeYoutubePickerModal();
}
if (ctx.state.runtimeOptionsModalOpen) {
runtimeOptionsModal.closeRuntimeOptionsModal();
}
@@ -416,6 +429,16 @@ function registerModalOpenHandlers(): void {
window.electronAPI.notifyOverlayModalOpened('jimaku');
});
});
window.electronAPI.onOpenYoutubeTrackPicker((payload) => {
runGuarded('youtube:picker-open', () => {
youtubePickerModal.openYoutubePickerModal(payload);
});
});
window.electronAPI.onCancelYoutubeTrackPicker(() => {
runGuarded('youtube:picker-cancel', () => {
youtubePickerModal.closeYoutubePickerModal();
});
});
window.electronAPI.onSubsyncManualOpen((payload: SubsyncManualPayload) => {
runGuarded('subsync:manual-open', () => {
subsyncModal.openSubsyncModal(payload);
@@ -528,6 +551,7 @@ async function init(): Promise<void> {
ctx.dom.secondarySubContainer.addEventListener('mouseleave', mouseHandlers.handleSecondaryMouseLeave);
mouseHandlers.setupResizeHandler();
mouseHandlers.setupPointerTracking();
mouseHandlers.setupSelectionObserver();
mouseHandlers.setupYomitanObserver();
setupDragDropToMpvQueue();
@@ -536,6 +560,7 @@ async function init(): Promise<void> {
});
jimakuModal.wireDomEvents();
youtubePickerModal.wireDomEvents();
kikuModal.wireDomEvents();
runtimeOptionsModal.wireDomEvents();
subsyncModal.wireDomEvents();