diff --git a/src/main/plugins/apiProvider.plugin.ts b/src/main/plugins/apiProvider.plugin.ts index d76bfec..787bfe5 100644 --- a/src/main/plugins/apiProvider.plugin.ts +++ b/src/main/plugins/apiProvider.plugin.ts @@ -1,11 +1,11 @@ import { ApiWorker, createApiWorker } from "@main/api/createApiWorker"; import { AfterInit, BaseProvider, OnDestroy } from "@main/utils/baseProvider"; import { IpcContext, IpcHandle, IpcOn } from "@main/utils/onIpcEvent"; -import type { App } from "electron"; +import { ipcMain, type App } from "electron"; import fetch from "node-fetch"; import Vibrant from "node-vibrant"; -import { API_ROUTES } from "../utils/eventNames"; +import IPC_EVENT_NAMES, { API_ROUTES } from "../utils/eventNames"; import TrackProvider from "./trackProvider.plugin"; @IpcContext @@ -94,7 +94,7 @@ export default class ApiProvider extends BaseProvider implements AfterInit, OnDe return isLiked; }); - return null; + return null; } @IpcHandle(API_ROUTES.TRACK_DISLIKE) async postTrackDisLike(_ev, like: boolean) { @@ -120,7 +120,7 @@ export default class ApiProvider extends BaseProvider implements AfterInit, OnDe } @IpcHandle(API_ROUTES.TRACK_CONTROL_NEXT) async nextTrack() { - return await this.windowContext.sendTrackControl("next") + return await this.windowContext.sendTrackControl("next"); } @IpcHandle(API_ROUTES.TRACK_CONTROL_FORWARD) async forwardTrack(_ev, data) { @@ -150,18 +150,33 @@ export default class ApiProvider extends BaseProvider implements AfterInit, OnDe } @IpcHandle(API_ROUTES.TRACK_CONTROL_PREV) async prevTrack() { - return await this.windowContext.sendTrackControl("prev") + return await this.windowContext.sendTrackControl("prev"); } @IpcHandle(API_ROUTES.TRACK_CONTROL_PLAY) async playTrack() { - return await this.windowContext.sendTrackControl("play") + return await this.windowContext + .sendTrackControl<{ data: { isPlaying: boolean; time: number }; type: any }>("play") + .then(({ data: { isPlaying, time } }) => { + ipcMain.emit(IPC_EVENT_NAMES.TRACK_PLAYSTATE, null, isPlaying, time); + return { isPlaying, time }; + }); } @IpcHandle(API_ROUTES.TRACK_CONTROL_PAUSE) async pauseTrack() { - return await this.windowContext.sendTrackControl("pause") + return await this.windowContext + .sendTrackControl<{ data: { isPlaying: boolean; time: number }; type: any }>("pause") + .then(({ data: { isPlaying, time } }) => { + ipcMain.emit(IPC_EVENT_NAMES.TRACK_PLAYSTATE, null, isPlaying, time); + return { isPlaying, time }; + }); } @IpcHandle(API_ROUTES.TRACK_CONTROL_TOGGLE_PLAY) async toggleTrackPlayback() { - return await this.windowContext.sendTrackControl("toggle") + return await this.windowContext + .sendTrackControl<{ data: { isPlaying: boolean; time: number }; type: any }>("toggle") + .then(({ data: { isPlaying, time } }) => { + ipcMain.emit(IPC_EVENT_NAMES.TRACK_PLAYSTATE, null, isPlaying, time); + return { isPlaying, time }; + }); } } diff --git a/src/main/plugins/client/track-api-controls.plugin.js b/src/main/plugins/client/track-api-controls.plugin.js index 657779b..20bf2cc 100644 --- a/src/main/plugins/client/track-api-controls.plugin.js +++ b/src/main/plugins/client/track-api-controls.plugin.js @@ -1,3 +1,4 @@ + export const meta = { name: "Api Control Handler", }; @@ -6,12 +7,33 @@ const trackControls = { toggle: player => { const state = player.getPlayerStateObject(); if (!state) return; - return (state.isPlaying || state.isOrWillBePlaying) ? player.stopVideo() : player.playVideo() + state.isPlaying ? player.pauseVideo() : player.playVideo() + return { + isPlaying: state.isPlaying, + time: player.getCurrentTime() + }; + }, + play: playerApi => { + playerApi.playVideo() + return { + isPlaying: true, + time: playerApi.getCurrentTime() + } + }, + pause: playerApi => { + playerApi.pauseVideo() + return { + isPlaying: false, + time: playerApi.getCurrentTime() + } }, - play: playerApi => playerApi.playVideo(), - pause: playerApi => playerApi.stopVideo(), next: playerApi => playerApi.nextVideo(), - prev: playerApi => playerApi.previousVideo() + prev: playerApi => playerApi.previousVideo(), + isPlaying: player => { + const state = player.getPlayerStateObject(); + if (!state) return; + return state.isPlaying; + } }; export const afterInit = () => { window.domUtils.ensureDomLoaded(() => { @@ -28,13 +50,21 @@ export const afterInit = () => { } } window.ipcRenderer.on("track:seek", setTimeSkip); - window.ipcRenderer.on("track:control", (_ev, data) => { - if (!data || typeof data === "object") return; + window.ipcRenderer.on("track:control", async (_ev, data) => { + if (!data || typeof data !== "object") return; const { type } = data; const handler = trackControls[type]; if (!handler) return; const playerApi = window.domUtils.playerApi(); - window.api.emit("track:control/response", type, handler(playerApi)) + // not ready yet + if (!playerApi) return; + + const handleResult = await Promise.resolve(handler(playerApi)); + window.api.emit("track:control/response", { + type, + data: handleResult + }) + }); }); }; diff --git a/src/main/plugins/mediaControlProvider.plugin.ts b/src/main/plugins/mediaControlProvider.plugin.ts index 264d669..9c66e2b 100644 --- a/src/main/plugins/mediaControlProvider.plugin.ts +++ b/src/main/plugins/mediaControlProvider.plugin.ts @@ -80,7 +80,6 @@ export default class MediaControlProvider @IpcOn(IPC_EVENT_NAMES.TRACK_PLAYSTATE) private __handleTrackMediaOSControl(_ev, isPlaying: boolean, progressSeconds: number = 0) { if (!this.mediaProviderEnabled()) return; - const { trackData } = this.getProvider("track"); if (!trackData) { this._mediaProvider.playbackStatus = MediaPlayerPlaybackStatus.Stopped; diff --git a/src/main/utils/mappedWindow.ts b/src/main/utils/mappedWindow.ts index dfe2985..213cc2f 100644 --- a/src/main/utils/mappedWindow.ts +++ b/src/main/utils/mappedWindow.ts @@ -2,7 +2,9 @@ import { BrowserWindow, WebContentsView } from "electron"; import IPC_EVENT_NAMES from "./eventNames"; type TrackControlTypes = StringLiteral<"play" | "pause" | "next" | "prev" | "toggle">; -type TrackControlFn = (type: TrackControlTypes) => Promise; +type TrackControlFn = ( + type: TrackControlTypes, +) => Promise; export interface BrowserWindowViews { main: BrowserWindow; views: { [key: string]: TView } & T; @@ -24,10 +26,12 @@ export function createWindowContext { main: BrowserWindow = _data.main; views: { [key: string]: TView } & T = _data.views || ({} as any); - async sendTrackControl(type: TrackControlTypes) { + async sendTrackControl(type: TrackControlTypes) { const view = this.views.youtubeView; if (!view) return Promise.reject(new Error("View not found")); - return await view.invoke(IPC_EVENT_NAMES.TRACK_CONTROL, { type }); + const data = ((await view.invoke(IPC_EVENT_NAMES.TRACK_CONTROL, { type })) ?? {}) as T; + if (data.type !== type) throw new Error("Invalid response"); + return data as T; } sendToAllViews(ev: string, ...args: any[]): void { return (Object.values(this.views) as TView[]) diff --git a/src/main/utils/onIpcEvent.ts b/src/main/utils/onIpcEvent.ts index f121853..b008e86 100644 --- a/src/main/utils/onIpcEvent.ts +++ b/src/main/utils/onIpcEvent.ts @@ -1,3 +1,5 @@ +import { stringifyJson } from "@main/lib/json"; +import { createLogger } from "@shared/utils/console"; import { Event, IpcMainEvent, IpcMainInvokeEvent } from "electron"; import { debounce } from "lodash-es"; import { serverMain } from "./serverEvents"; @@ -10,6 +12,7 @@ interface IpcContextEvent { debounce?: number; filter?: IpcFilterOption | ((...args: any[]) => boolean); passive?: boolean; + debug?: boolean }; } export function IpcContextWithOptions() { @@ -25,7 +28,9 @@ export function IpcContext(IpcContextBas const symbols: any = IpcContextBase.prototype[classIpcStoreSymbol]; if (symbols) { symbols.forEach(({ name, type, options }: IpcContextEvent, method: string) => { + const log = createLogger("IPC").child(`${name}:ipc.${type ?? 'on'}`); const func = (...args: any[]) => { + if (options?.debug) log.debug(`hit, payload size: ${new Blob([stringifyJson(args ?? null)]).size} bytes`); if ( typeof (this as any)[method] === "function" && (options && options.filter && typeof options.filter === "function" diff --git a/src/preload/base.ts b/src/preload/base.ts index 9a46183..cd606a0 100644 --- a/src/preload/base.ts +++ b/src/preload/base.ts @@ -93,8 +93,13 @@ export default { ), ); }, - playerApi: () => - (document.querySelector("ytmusic-app-layout>ytmusic-player-bar") as any)?.playerApi, + playerApi: (() => { + let playerApiCache: any; + return () => + playerApiCache || + (playerApiCache = (document.querySelector("ytmusic-app-layout>ytmusic-player-bar") as any) + ?.playerApi); + })(), setInteractiveElements: (interactiveElements: T[]) => { let isMouseOverInteractiveElement = false; ipcRenderer.send("set-ignore-mouse-events", true, { forward: true });