fix: fixed an issue that caused infinite loops when using Music Together

fix #1752
This commit is contained in:
JellyBrick
2024-02-20 12:57:26 +09:00
parent 62a86e9267
commit 6f70d179c7
3 changed files with 40 additions and 27 deletions

View File

@ -6,9 +6,9 @@ import { t } from '@/i18n';
import { createPlugin } from '@/utils'; import { createPlugin } from '@/utils';
import promptOptions from '@/providers/prompt-options'; import promptOptions from '@/providers/prompt-options';
import { AppAPI, getDefaultProfile, Permission, Profile, VideoData } from './types'; import { type AppElement, getDefaultProfile, type Permission, type Profile, type VideoData } from './types';
import { Queue } from './queue'; import { Queue } from './queue';
import { Connection, ConnectionEventUnion } from './connection'; import { Connection, type ConnectionEventUnion } from './connection';
import { createHostPopup } from './ui/host'; import { createHostPopup } from './ui/host';
import { createGuestPopup } from './ui/guest'; import { createGuestPopup } from './ui/guest';
import { createSettingPopup } from './ui/setting'; import { createSettingPopup } from './ui/setting';
@ -41,7 +41,7 @@ export default createPlugin<
{ {
connection?: Connection; connection?: Connection;
ipc?: RendererContext<never>['ipc']; ipc?: RendererContext<never>['ipc'];
api: HTMLElement & AppAPI | null; api: AppElement | null;
queue?: Queue; queue?: Queue;
playerApi?: YoutubePlayer; playerApi?: YoutubePlayer;
showPrompt: (title: string, label: string) => Promise<string>; showPrompt: (title: string, label: string) => Promise<string>;
@ -557,7 +557,7 @@ export default createPlugin<
start({ ipc }) { start({ ipc }) {
this.ipc = ipc; this.ipc = ipc;
this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>; this.showPrompt = async (title: string, label: string) => ipc.invoke('music-together:prompt', title, label) as Promise<string>;
this.api = document.querySelector<HTMLElement & AppAPI>('ytmusic-app'); this.api = document.querySelector<AppElement>('ytmusic-app');
/* setup */ /* setup */
document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML); document.querySelector('#right-content > ytmusic-settings-button')?.insertAdjacentHTML('beforebegin', settingHTML);

View File

@ -1,10 +1,10 @@
import { getMusicQueueRenderer } from './song'; import { getMusicQueueRenderer } from './song';
import { mapQueueItem } from './utils'; import { mapQueueItem } from './utils';
import { ConnectionEventUnion } from '@/plugins/music-together/connection';
import { t } from '@/i18n'; import { t } from '@/i18n';
import type { Profile, QueueAPI, VideoData } from '../types'; import type { ConnectionEventUnion } from '@/plugins/music-together/connection';
import type { Profile, QueueElement, VideoData } from '../types';
import type { QueueItem } from '@/types/datahost-get-state'; import type { QueueItem } from '@/types/datahost-get-state';
const getHeaderPayload = (() => { const getHeaderPayload = (() => {
@ -103,26 +103,29 @@ const getHeaderPayload = (() => {
export type QueueOptions = { export type QueueOptions = {
videoList?: VideoData[]; videoList?: VideoData[];
owner?: Profile; owner?: Profile;
queue?: HTMLElement & QueueAPI; queue?: QueueElement;
getProfile: (id: string) => Profile | undefined; getProfile: (id: string) => Profile | undefined;
} }
export type QueueEventListener = (event: ConnectionEventUnion) => void; export type QueueEventListener = (event: ConnectionEventUnion) => void;
export class Queue { export class Queue {
private queue: (HTMLElement & QueueAPI); private readonly queue: QueueElement;
private originalDispatch?: (obj: { private originalDispatch?: (obj: {
type: string; type: string;
payload?: { items?: QueueItem[] | undefined; }; payload?: { items?: QueueItem[] | undefined; };
}) => void; }) => void;
private internalDispatch = false; private internalDispatch = false;
private ignoreFlag = false; private ignoreFlag = false;
private listeners: QueueEventListener[] = []; private listeners: QueueEventListener[] = [];
private owner: Profile | null = null;
private getProfile: (id: string) => Profile | undefined; private owner: Profile | null;
private readonly getProfile: (id: string) => Profile | undefined;
constructor(options: QueueOptions) { constructor(options: QueueOptions) {
this.getProfile = options.getProfile; this.getProfile = options.getProfile;
this.queue = options.queue ?? document.querySelector<HTMLElement & QueueAPI>('#queue')!; this.queue = options.queue ?? (document.querySelector<QueueElement>('#queue')!);
this.owner = options.owner ?? null; this.owner = options.owner ?? null;
this._videoList = options.videoList ?? []; this._videoList = options.videoList ?? [];
} }
@ -135,11 +138,11 @@ export class Queue {
} }
get selectedIndex() { get selectedIndex() {
return mapQueueItem((it) => it?.selected, this.queue.store.getState().queue.items).findIndex(Boolean) ?? 0; return mapQueueItem((it) => it?.selected, this.queue.queue.store.store.getState().queue.items).findIndex(Boolean) ?? 0;
} }
get rawItems() { get rawItems() {
return this.queue?.store.getState().queue.items; return this.queue?.queue.store.store.getState().queue.items;
} }
get flatItems() { get flatItems() {
@ -169,8 +172,8 @@ export class Queue {
this.queue?.dispatch({ this.queue?.dispatch({
type: 'ADD_ITEMS', type: 'ADD_ITEMS',
payload: { payload: {
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId, nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
index: index ?? this.queue.store.getState().queue.items.length ?? 0, index: index ?? this.queue.queue.store.store.getState().queue.items.length ?? 0,
items, items,
shuffleEnabled: false, shuffleEnabled: false,
shouldAssignIds: true shouldAssignIds: true
@ -249,7 +252,7 @@ export class Queue {
return; return;
} }
if (this.originalDispatch) this.queue.store.dispatch = this.originalDispatch; if (this.originalDispatch) this.queue.queue.store.store.dispatch = this.originalDispatch;
} }
injection() { injection() {
@ -258,8 +261,8 @@ export class Queue {
return; return;
} }
this.originalDispatch = this.queue.store.dispatch; this.originalDispatch = this.queue.queue.store.store.dispatch;
this.queue.store.dispatch = (event) => { this.queue.queue.store.store.dispatch = (event) => {
if (!this.queue || !this.owner) { if (!this.queue || !this.owner) {
console.error('Queue is not initialized!'); console.error('Queue is not initialized!');
return; return;
@ -361,10 +364,13 @@ export class Queue {
const fakeContext = { const fakeContext = {
...this.queue, ...this.queue,
queue: {
...this.queue.queue,
store: { store: {
...this.queue.store, ...this.queue.queue.store,
dispatch: this.originalDispatch dispatch: this.originalDispatch,
} }
},
}; };
this.originalDispatch?.call(fakeContext, event); this.originalDispatch?.call(fakeContext, event);
}; };
@ -400,7 +406,7 @@ export class Queue {
type: 'UPDATE_ITEMS', type: 'UPDATE_ITEMS',
payload: { payload: {
items: items, items: items,
nextQueueItemId: this.queue.store.getState().queue.nextQueueItemId, nextQueueItemId: this.queue.queue.store.store.getState().queue.nextQueueItemId,
shouldAssignIds: true, shouldAssignIds: true,
currentIndex: -1 currentIndex: -1
} }

View File

@ -1,5 +1,5 @@
import { YoutubePlayer } from '@/types/youtube-player'; import type { YoutubePlayer } from '@/types/youtube-player';
import { GetState, QueueItem } from '@/types/datahost-get-state'; import type { GetState, QueueItem } from '@/types/datahost-get-state';
type StoreState = GetState; type StoreState = GetState;
type Store = { type Store = {
@ -14,16 +14,23 @@ type Store = {
replaceReducer: (param1: unknown) => unknown; replaceReducer: (param1: unknown) => unknown;
subscribe: (callback: () => void) => unknown; subscribe: (callback: () => void) => unknown;
} }
export type QueueAPI = {
export type QueueElement = HTMLElement & {
dispatch(obj: { dispatch(obj: {
type: string; type: string;
payload?: unknown; payload?: unknown;
}): void; }): void;
queue: QueueAPI;
};
export type QueueAPI = {
getItems(): unknown[]; getItems(): unknown[];
store: Store; store: {
store: Store,
};
continuation?: string; continuation?: string;
autoPlaying?: boolean; autoPlaying?: boolean;
}; };
export type AppElement = HTMLElement & AppAPI;
export type AppAPI = { export type AppAPI = {
queue_: QueueAPI; queue_: QueueAPI;
playerApi_: YoutubePlayer; playerApi_: YoutubePlayer;