mirror of
https://github.com/ksyasuda/SubMiner.git
synced 2026-02-28 06:22:45 -08:00
pretty
This commit is contained in:
@@ -1,5 +1,5 @@
|
||||
import { EventEmitter } from "events";
|
||||
import { Config, MpvClient, MpvSubtitleRenderMetrics } from "../../types";
|
||||
import { EventEmitter } from 'events';
|
||||
import { Config, MpvClient, MpvSubtitleRenderMetrics } from '../../types';
|
||||
import {
|
||||
dispatchMpvProtocolMessage,
|
||||
MPV_REQUEST_ID_TRACK_LIST_AUDIO,
|
||||
@@ -7,21 +7,18 @@ import {
|
||||
MpvMessage,
|
||||
MpvProtocolHandleMessageDeps,
|
||||
splitMpvMessagesFromBuffer,
|
||||
} from "./mpv-protocol";
|
||||
import {
|
||||
requestMpvInitialState,
|
||||
subscribeToMpvProperties,
|
||||
} from "./mpv-properties";
|
||||
import { scheduleMpvReconnect, MpvSocketTransport } from "./mpv-transport";
|
||||
import { createLogger } from "../../logger";
|
||||
} from './mpv-protocol';
|
||||
import { requestMpvInitialState, subscribeToMpvProperties } from './mpv-properties';
|
||||
import { scheduleMpvReconnect, MpvSocketTransport } from './mpv-transport';
|
||||
import { createLogger } from '../../logger';
|
||||
|
||||
const logger = createLogger("main:mpv");
|
||||
const logger = createLogger('main:mpv');
|
||||
|
||||
export type MpvTrackProperty = {
|
||||
type?: string;
|
||||
id?: number;
|
||||
selected?: boolean;
|
||||
"ff-index"?: number;
|
||||
'ff-index'?: number;
|
||||
};
|
||||
|
||||
export function resolveCurrentAudioStreamIndex(
|
||||
@@ -32,17 +29,13 @@ export function resolveCurrentAudioStreamIndex(
|
||||
return null;
|
||||
}
|
||||
|
||||
const audioTracks = tracks.filter((track) => track.type === "audio");
|
||||
const audioTracks = tracks.filter((track) => track.type === 'audio');
|
||||
const activeTrack =
|
||||
audioTracks.find((track) => track.id === currentAudioTrackId) ||
|
||||
audioTracks.find((track) => track.selected === true);
|
||||
|
||||
const ffIndex = activeTrack?.["ff-index"];
|
||||
return typeof ffIndex === "number" &&
|
||||
Number.isInteger(ffIndex) &&
|
||||
ffIndex >= 0
|
||||
? ffIndex
|
||||
: null;
|
||||
const ffIndex = activeTrack?.['ff-index'];
|
||||
return typeof ffIndex === 'number' && Number.isInteger(ffIndex) && ffIndex >= 0 ? ffIndex : null;
|
||||
}
|
||||
|
||||
export interface MpvRuntimeClientLike {
|
||||
@@ -59,22 +52,18 @@ export function showMpvOsdRuntime(
|
||||
fallbackLog: (text: string) => void = (line) => logger.info(line),
|
||||
): void {
|
||||
if (mpvClient && mpvClient.connected) {
|
||||
mpvClient.send({ command: ["show-text", text, "3000"] });
|
||||
mpvClient.send({ command: ['show-text', text, '3000'] });
|
||||
return;
|
||||
}
|
||||
fallbackLog(`OSD (MPV not connected): ${text}`);
|
||||
}
|
||||
|
||||
export function replayCurrentSubtitleRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
): void {
|
||||
export function replayCurrentSubtitleRuntime(mpvClient: MpvRuntimeClientLike | null): void {
|
||||
if (!mpvClient?.replayCurrentSubtitle) return;
|
||||
mpvClient.replayCurrentSubtitle();
|
||||
}
|
||||
|
||||
export function playNextSubtitleRuntime(
|
||||
mpvClient: MpvRuntimeClientLike | null,
|
||||
): void {
|
||||
export function playNextSubtitleRuntime(mpvClient: MpvRuntimeClientLike | null): void {
|
||||
if (!mpvClient?.playNextSubtitle) return;
|
||||
mpvClient.playNextSubtitle();
|
||||
}
|
||||
@@ -95,7 +84,7 @@ export function setMpvSubVisibilityRuntime(
|
||||
mpvClient.setSubVisibility(visible);
|
||||
}
|
||||
|
||||
export { MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY } from "./mpv-protocol";
|
||||
export { MPV_REQUEST_ID_SECONDARY_SUB_VISIBILITY } from './mpv-protocol';
|
||||
|
||||
export interface MpvIpcClientProtocolDeps {
|
||||
getResolvedConfig: () => Config;
|
||||
@@ -110,17 +99,17 @@ export interface MpvIpcClientProtocolDeps {
|
||||
export interface MpvIpcClientDeps extends MpvIpcClientProtocolDeps {}
|
||||
|
||||
export interface MpvIpcClientEventMap {
|
||||
"connection-change": { connected: boolean };
|
||||
"subtitle-change": { text: string; isOverlayVisible: boolean };
|
||||
"subtitle-ass-change": { text: string };
|
||||
"subtitle-timing": { text: string; start: number; end: number };
|
||||
"time-pos-change": { time: number };
|
||||
"pause-change": { paused: boolean };
|
||||
"secondary-subtitle-change": { text: string };
|
||||
"media-path-change": { path: string };
|
||||
"media-title-change": { title: string | null };
|
||||
"subtitle-metrics-change": { patch: Partial<MpvSubtitleRenderMetrics> };
|
||||
"secondary-subtitle-visibility": { visible: boolean };
|
||||
'connection-change': { connected: boolean };
|
||||
'subtitle-change': { text: string; isOverlayVisible: boolean };
|
||||
'subtitle-ass-change': { text: string };
|
||||
'subtitle-timing': { text: string; start: number; end: number };
|
||||
'time-pos-change': { time: number };
|
||||
'pause-change': { paused: boolean };
|
||||
'secondary-subtitle-change': { text: string };
|
||||
'media-path-change': { path: string };
|
||||
'media-title-change': { title: string | null };
|
||||
'subtitle-metrics-change': { patch: Partial<MpvSubtitleRenderMetrics> };
|
||||
'secondary-subtitle-visibility': { visible: boolean };
|
||||
}
|
||||
|
||||
type MpvIpcClientEventName = keyof MpvIpcClientEventMap;
|
||||
@@ -128,20 +117,20 @@ type MpvIpcClientEventName = keyof MpvIpcClientEventMap;
|
||||
export class MpvIpcClient implements MpvClient {
|
||||
private deps: MpvIpcClientProtocolDeps;
|
||||
private transport: MpvSocketTransport;
|
||||
public socket: ReturnType<MpvSocketTransport["getSocket"]> = null;
|
||||
public socket: ReturnType<MpvSocketTransport['getSocket']> = null;
|
||||
private eventBus = new EventEmitter();
|
||||
private buffer = "";
|
||||
private buffer = '';
|
||||
public connected = false;
|
||||
private connecting = false;
|
||||
private reconnectAttempt = 0;
|
||||
private firstConnection = true;
|
||||
private hasConnectedOnce = false;
|
||||
public currentVideoPath = "";
|
||||
public currentVideoPath = '';
|
||||
public currentTimePos = 0;
|
||||
public currentSubStart = 0;
|
||||
public currentSubEnd = 0;
|
||||
public currentSubText = "";
|
||||
public currentSecondarySubText = "";
|
||||
public currentSubText = '';
|
||||
public currentSecondarySubText = '';
|
||||
public currentAudioStreamIndex: number | null = null;
|
||||
private currentAudioTrackId: number | null = null;
|
||||
private mpvSubtitleRenderMetrics: MpvSubtitleRenderMetrics = {
|
||||
@@ -150,13 +139,13 @@ export class MpvIpcClient implements MpvClient {
|
||||
subScale: 1,
|
||||
subMarginY: 0,
|
||||
subMarginX: 0,
|
||||
subFont: "",
|
||||
subFont: '',
|
||||
subSpacing: 0,
|
||||
subBold: false,
|
||||
subItalic: false,
|
||||
subBorderSize: 0,
|
||||
subShadowOffset: 0,
|
||||
subAssOverride: "yes",
|
||||
subAssOverride: 'yes',
|
||||
subScaleByWindow: true,
|
||||
subUseMargins: true,
|
||||
osdHeight: 0,
|
||||
@@ -174,11 +163,11 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.transport = new MpvSocketTransport({
|
||||
socketPath,
|
||||
onConnect: () => {
|
||||
logger.debug("Connected to MPV socket");
|
||||
logger.debug('Connected to MPV socket');
|
||||
this.connected = true;
|
||||
this.connecting = false;
|
||||
this.socket = this.transport.getSocket();
|
||||
this.emit("connection-change", { connected: true });
|
||||
this.emit('connection-change', { connected: true });
|
||||
this.reconnectAttempt = 0;
|
||||
this.hasConnectedOnce = true;
|
||||
this.setSecondarySubVisibility(false);
|
||||
@@ -186,10 +175,9 @@ export class MpvIpcClient implements MpvClient {
|
||||
requestMpvInitialState(this.send.bind(this));
|
||||
|
||||
const shouldAutoStart =
|
||||
this.deps.autoStartOverlay ||
|
||||
this.deps.getResolvedConfig().auto_start_overlay === true;
|
||||
this.deps.autoStartOverlay || this.deps.getResolvedConfig().auto_start_overlay === true;
|
||||
if (this.firstConnection && shouldAutoStart) {
|
||||
logger.debug("Auto-starting overlay, hiding mpv subtitles");
|
||||
logger.debug('Auto-starting overlay, hiding mpv subtitles');
|
||||
setTimeout(() => {
|
||||
this.deps.setOverlayVisible(true);
|
||||
}, 100);
|
||||
@@ -204,15 +192,15 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.processBuffer();
|
||||
},
|
||||
onError: (err: Error) => {
|
||||
logger.debug("MPV socket error:", err.message);
|
||||
logger.debug('MPV socket error:', err.message);
|
||||
this.failPendingRequests();
|
||||
},
|
||||
onClose: () => {
|
||||
logger.debug("MPV socket closed");
|
||||
logger.debug('MPV socket closed');
|
||||
this.connected = false;
|
||||
this.connecting = false;
|
||||
this.socket = null;
|
||||
this.emit("connection-change", { connected: false });
|
||||
this.emit('connection-change', { connected: false });
|
||||
this.failPendingRequests();
|
||||
this.scheduleReconnect();
|
||||
},
|
||||
@@ -240,14 +228,12 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.eventBus.emit(event as string, payload);
|
||||
}
|
||||
|
||||
private emitSubtitleMetricsChange(
|
||||
patch: Partial<MpvSubtitleRenderMetrics>,
|
||||
): void {
|
||||
private emitSubtitleMetricsChange(patch: Partial<MpvSubtitleRenderMetrics>): void {
|
||||
this.mpvSubtitleRenderMetrics = {
|
||||
...this.mpvSubtitleRenderMetrics,
|
||||
...patch,
|
||||
};
|
||||
this.emit("subtitle-metrics-change", { patch });
|
||||
this.emit('subtitle-metrics-change', { patch });
|
||||
}
|
||||
|
||||
setSocketPath(socketPath: string): void {
|
||||
@@ -262,7 +248,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
return;
|
||||
}
|
||||
|
||||
logger.info("MPV IPC connect requested.");
|
||||
logger.info('MPV IPC connect requested.');
|
||||
this.connecting = true;
|
||||
this.transport.connect();
|
||||
}
|
||||
@@ -274,9 +260,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
getReconnectTimer: () => this.deps.getReconnectTimer(),
|
||||
setReconnectTimer: (timer) => this.deps.setReconnectTimer(timer),
|
||||
onReconnectAttempt: (attempt, delay) => {
|
||||
logger.debug(
|
||||
`Attempting to reconnect to MPV (attempt ${attempt}, delay ${delay}ms)...`,
|
||||
);
|
||||
logger.debug(`Attempting to reconnect to MPV (attempt ${attempt}, delay ${delay}ms)...`);
|
||||
},
|
||||
connect: () => {
|
||||
this.connect();
|
||||
@@ -291,7 +275,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.handleMessage(message);
|
||||
},
|
||||
(line, error) => {
|
||||
logger.error("Failed to parse MPV message:", line, error);
|
||||
logger.error('Failed to parse MPV message:', line, error);
|
||||
},
|
||||
);
|
||||
this.buffer = parsed.nextBuffer;
|
||||
@@ -307,22 +291,22 @@ export class MpvIpcClient implements MpvClient {
|
||||
getSubtitleMetrics: () => this.mpvSubtitleRenderMetrics,
|
||||
isVisibleOverlayVisible: () => this.deps.isVisibleOverlayVisible(),
|
||||
emitSubtitleChange: (payload) => {
|
||||
this.emit("subtitle-change", payload);
|
||||
this.emit('subtitle-change', payload);
|
||||
},
|
||||
emitSubtitleAssChange: (payload) => {
|
||||
this.emit("subtitle-ass-change", payload);
|
||||
this.emit('subtitle-ass-change', payload);
|
||||
},
|
||||
emitSubtitleTiming: (payload) => {
|
||||
this.emit("subtitle-timing", payload);
|
||||
this.emit('subtitle-timing', payload);
|
||||
},
|
||||
emitTimePosChange: (payload) => {
|
||||
this.emit("time-pos-change", payload);
|
||||
this.emit('time-pos-change', payload);
|
||||
},
|
||||
emitPauseChange: (payload) => {
|
||||
this.emit("pause-change", payload);
|
||||
this.emit('pause-change', payload);
|
||||
},
|
||||
emitSecondarySubtitleChange: (payload) => {
|
||||
this.emit("secondary-subtitle-change", payload);
|
||||
this.emit('secondary-subtitle-change', payload);
|
||||
},
|
||||
getCurrentSubText: () => this.currentSubText,
|
||||
setCurrentSubText: (text: string) => {
|
||||
@@ -337,10 +321,10 @@ export class MpvIpcClient implements MpvClient {
|
||||
},
|
||||
getCurrentSubEnd: () => this.currentSubEnd,
|
||||
emitMediaPathChange: (payload) => {
|
||||
this.emit("media-path-change", payload);
|
||||
this.emit('media-path-change', payload);
|
||||
},
|
||||
emitMediaTitleChange: (payload) => {
|
||||
this.emit("media-title-change", payload);
|
||||
this.emit('media-title-change', payload);
|
||||
},
|
||||
emitSubtitleMetricsChange: (patch) => {
|
||||
this.emitSubtitleMetricsChange(patch);
|
||||
@@ -350,8 +334,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
},
|
||||
resolvePendingRequest: (requestId: number, message: MpvMessage) =>
|
||||
this.tryResolvePendingRequest(requestId, message),
|
||||
setSecondarySubVisibility: (visible: boolean) =>
|
||||
this.setSecondarySubVisibility(visible),
|
||||
setSecondarySubVisibility: (visible: boolean) => this.setSecondarySubVisibility(visible),
|
||||
syncCurrentAudioStreamIndex: () => {
|
||||
this.syncCurrentAudioStreamIndex();
|
||||
},
|
||||
@@ -377,7 +360,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
this.currentVideoPath = value;
|
||||
},
|
||||
emitSecondarySubtitleVisibility: (payload) => {
|
||||
this.emit("secondary-subtitle-visibility", payload);
|
||||
this.emit('secondary-subtitle-visibility', payload);
|
||||
},
|
||||
setPreviousSecondarySubVisibility: (visible: boolean) => {
|
||||
this.previousSecondarySubVisibility = visible;
|
||||
@@ -400,7 +383,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
|
||||
setTimeout(() => {
|
||||
this.send({
|
||||
command: ["get_property", "track-list"],
|
||||
command: ['get_property', 'track-list'],
|
||||
request_id: MPV_REQUEST_ID_TRACK_LIST_SECONDARY,
|
||||
});
|
||||
}, 500);
|
||||
@@ -408,7 +391,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
|
||||
private syncCurrentAudioStreamIndex(): void {
|
||||
this.send({
|
||||
command: ["get_property", "track-list"],
|
||||
command: ['get_property', 'track-list'],
|
||||
request_id: MPV_REQUEST_ID_TRACK_LIST_AUDIO,
|
||||
});
|
||||
}
|
||||
@@ -418,13 +401,10 @@ export class MpvIpcClient implements MpvClient {
|
||||
type?: string;
|
||||
id?: number;
|
||||
selected?: boolean;
|
||||
"ff-index"?: number;
|
||||
'ff-index'?: number;
|
||||
}>,
|
||||
): void {
|
||||
this.currentAudioStreamIndex = resolveCurrentAudioStreamIndex(
|
||||
tracks,
|
||||
this.currentAudioTrackId,
|
||||
);
|
||||
this.currentAudioStreamIndex = resolveCurrentAudioStreamIndex(tracks, this.currentAudioTrackId);
|
||||
}
|
||||
|
||||
send(command: { command: unknown[]; request_id?: number }): boolean {
|
||||
@@ -437,7 +417,7 @@ export class MpvIpcClient implements MpvClient {
|
||||
request(command: unknown[]): Promise<MpvMessage> {
|
||||
return new Promise((resolve, reject) => {
|
||||
if (!this.connected || !this.socket) {
|
||||
reject(new Error("MPV not connected"));
|
||||
reject(new Error('MPV not connected'));
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -446,39 +426,34 @@ export class MpvIpcClient implements MpvClient {
|
||||
const sent = this.send({ command, request_id: requestId });
|
||||
if (!sent) {
|
||||
this.pendingRequests.delete(requestId);
|
||||
reject(new Error("Failed to send MPV request"));
|
||||
reject(new Error('Failed to send MPV request'));
|
||||
return;
|
||||
}
|
||||
|
||||
setTimeout(() => {
|
||||
if (this.pendingRequests.delete(requestId)) {
|
||||
reject(new Error("MPV request timed out"));
|
||||
reject(new Error('MPV request timed out'));
|
||||
}
|
||||
}, 4000);
|
||||
});
|
||||
}
|
||||
|
||||
async requestProperty(name: string): Promise<unknown> {
|
||||
const response = await this.request(["get_property", name]);
|
||||
if (response.error && response.error !== "success") {
|
||||
throw new Error(
|
||||
`Failed to read MPV property '${name}': ${response.error}`,
|
||||
);
|
||||
const response = await this.request(['get_property', name]);
|
||||
if (response.error && response.error !== 'success') {
|
||||
throw new Error(`Failed to read MPV property '${name}': ${response.error}`);
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
private failPendingRequests(): void {
|
||||
for (const [requestId, resolve] of this.pendingRequests.entries()) {
|
||||
resolve({ request_id: requestId, error: "disconnected" });
|
||||
resolve({ request_id: requestId, error: 'disconnected' });
|
||||
}
|
||||
this.pendingRequests.clear();
|
||||
}
|
||||
|
||||
private tryResolvePendingRequest(
|
||||
requestId: number,
|
||||
message: MpvMessage,
|
||||
): boolean {
|
||||
private tryResolvePendingRequest(requestId: number, message: MpvMessage): boolean {
|
||||
const pending = this.pendingRequests.get(requestId);
|
||||
if (!pending) {
|
||||
return false;
|
||||
@@ -490,40 +465,32 @@ export class MpvIpcClient implements MpvClient {
|
||||
|
||||
setSubVisibility(visible: boolean): void {
|
||||
this.send({
|
||||
command: ["set_property", "sub-visibility", visible ? "yes" : "no"],
|
||||
command: ['set_property', 'sub-visibility', visible ? 'yes' : 'no'],
|
||||
});
|
||||
}
|
||||
|
||||
replayCurrentSubtitle(): void {
|
||||
this.pendingPauseAtSubEnd = true;
|
||||
this.send({ command: ["sub-seek", 0] });
|
||||
this.send({ command: ['sub-seek', 0] });
|
||||
}
|
||||
|
||||
playNextSubtitle(): void {
|
||||
this.pendingPauseAtSubEnd = true;
|
||||
this.send({ command: ["sub-seek", 1] });
|
||||
this.send({ command: ['sub-seek', 1] });
|
||||
}
|
||||
|
||||
restorePreviousSecondarySubVisibility(): void {
|
||||
const previous = this.previousSecondarySubVisibility;
|
||||
if (previous === null) return;
|
||||
this.send({
|
||||
command: [
|
||||
"set_property",
|
||||
"secondary-sub-visibility",
|
||||
previous ? "yes" : "no",
|
||||
],
|
||||
command: ['set_property', 'secondary-sub-visibility', previous ? 'yes' : 'no'],
|
||||
});
|
||||
this.previousSecondarySubVisibility = null;
|
||||
}
|
||||
|
||||
private setSecondarySubVisibility(visible: boolean): void {
|
||||
this.send({
|
||||
command: [
|
||||
"set_property",
|
||||
"secondary-sub-visibility",
|
||||
visible ? "yes" : "no",
|
||||
],
|
||||
command: ['set_property', 'secondary-sub-visibility', visible ? 'yes' : 'no'],
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user