mirror of
https://github.com/th-ch/youtube-music.git
synced 2026-01-11 10:31:47 +00:00
fix: fixed an issue that caused infinite loops when using Music Together
fix #1752
This commit is contained in:
@ -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);
|
||||||
|
|||||||
@ -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
|
||||||
}
|
}
|
||||||
|
|||||||
@ -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;
|
||||||
|
|||||||
Reference in New Issue
Block a user