Move YTM To WS Only

This commit is contained in:
thororen1234 2025-08-07 03:58:01 -04:00
parent d06fcf84e0
commit ca7c8c02d5
No known key found for this signature in database
5 changed files with 65 additions and 122 deletions

View file

@ -109,16 +109,16 @@ function makeContextMenu(name: string, path: string) {
}
function Controls() {
const [isPlaying, repeat] = useStateFromStores<[boolean, Repeat]>(
const [isPlaying, shuffle, repeat] = useStateFromStores(
[YoutubeMusicStore],
() => [YoutubeMusicStore.isPlaying, YoutubeMusicStore.repeat]
() => [YoutubeMusicStore.isPlaying, YoutubeMusicStore.shuffle, YoutubeMusicStore.repeat]
);
const [nextRepeat, repeatClassName] = (() => {
switch (repeat) {
case "NONE": return ["context", "repeat-off"] as const;
case "ALL": return ["track", "repeat-context"] as const;
case "ONE": return ["off", "repeat-track"] as const;
case "NONE": return ["ALL", "repeat-off"] as const;
case "ALL": return ["ONE", "repeat-context"] as const;
case "ONE": return ["NONE", "repeat-track"] as const;
default: throw new Error(`Invalid repeat state ${repeat}`);
}
})();
@ -128,11 +128,11 @@ function Controls() {
<Flex className={cl("button-row")} style={{ gap: 0 }}>
<Button
className={classes(cl("button"), cl("shuffle-off"))}
onClick={() => YoutubeMusicStore.shuffle()}
onClick={() => YoutubeMusicStore.setShuffle(!shuffle)}
>
<Shuffle />
</Button>
<Button onClick={() => YoutubeMusicStore.prev()}>
<Button onClick={() => YoutubeMusicStore.previous()}>
<SkipPrev />
</Button>
<Button onClick={() => YoutubeMusicStore.setPlaying(!isPlaying)}>
@ -143,8 +143,7 @@ function Controls() {
</Button>
<Button
className={classes(cl("button"), cl(repeatClassName))}
onClick={() => YoutubeMusicStore.switchRepeat()}
style={{ position: "relative" }}
onClick={() => YoutubeMusicStore.setRepeat(nextRepeat)}
>
{repeat === "ONE" && <span className={cl("repeat-1")}>1</span>}
<Repeat />
@ -158,28 +157,30 @@ const seek = debounce((v: number) => {
});
function YtmSeekBar() {
const { songDuration } = YoutubeMusicStore.song!;
const { songDuration, videoId } = YoutubeMusicStore.song ?? {};
const [storePosition, isSettingPosition, isPlaying] = useStateFromStores(
const [storePosition, isPlaying] = useStateFromStores(
[YoutubeMusicStore],
() => [YoutubeMusicStore.mPosition, YoutubeMusicStore.isSettingPosition, YoutubeMusicStore.isPlaying]
() => [YoutubeMusicStore.mPosition, YoutubeMusicStore.isPlaying]
);
const [position, setPosition] = useState<number>(storePosition);
useEffect(() => {
if (isPlaying && !isSettingPosition) {
setPosition(storePosition);
}, [videoId, songDuration, storePosition]);
useEffect(() => {
if (isPlaying) {
setPosition(YoutubeMusicStore.position);
const interval = setInterval(() => {
setPosition(p => p + 1000);
}, 1000);
return () => clearInterval(interval);
}
}, [storePosition, isSettingPosition, isPlaying]);
}, [storePosition, isPlaying]);
const onChange = (v: number) => {
if (isSettingPosition) return;
setPosition(v);
seek(v);
};

View file

@ -55,25 +55,13 @@ export interface PlayerState {
export type Repeat = "NONE" | "ONE" | "ALL";
const nextRepeat = (repeat: Repeat) => {
switch (repeat) {
case "NONE":
return "ALL" as const;
case "ALL":
return "ONE" as const;
case "ONE":
return "NONE" as const;
}
};
const logger = new Logger("YoutubeMusicControls");
type Message = { type: "PLAYER_STATE"; } & PlayerState;
class YoutubemusicSocket {
private PORT = 26539;
class YoutubeMusicSocket {
public onChange: (e: Message) => void;
private ready = false;
public ready = false;
private socket: WebSocket | undefined;
@ -97,7 +85,7 @@ class YoutubemusicSocket {
return {
"play": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "play" })),
"pause": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "pause" })),
"prev": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "prev" })),
"previous": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "previous" })),
"next": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "next" })),
"seek": (seconds: number) => this.socket?.send(JSON.stringify({ type: "ACTION", action: "seek", data: seconds })),
"shuffle": () => this.socket?.send(JSON.stringify({ type: "ACTION", action: "shuffle" })),
@ -115,7 +103,6 @@ class YoutubemusicSocket {
this.socket = new WebSocket(url);
this.socket.addEventListener("open", () => {
// logger.info("Connected to YouTube Music WebSocket");
this.ready = true;
this.routes.pause();
this.routes.play();
@ -129,10 +116,8 @@ class YoutubemusicSocket {
});
this.socket.addEventListener("close", e => {
// logger.info("YouTube Music Socket Disconnected:", e.code, e.reason);
this.ready = false;
if (!this.ready) setTimeout(() => this.reconnect(), 10_000);
// this.tryConnect();
this.onChange({ type: "PLAYER_STATE", song: null, isPlaying: false, position: 0, repeat: "NONE", volume: 0 });
});
@ -167,11 +152,10 @@ export const YoutubeMusicStore = proxyLazyWebpack(() => {
public song: Song | null = null;
public isPlaying = false;
public repeat: Repeat = "NONE";
public shuffle = false;
public volume = 0;
public isSettingPosition = false;
public socket = new YoutubemusicSocket((message: Message) => {
public socket = new YoutubeMusicSocket((message: Message) => {
if (message.song) {
store.song = message.song;
store.position = message.song.elapsedSeconds ?? 0;
@ -181,7 +165,6 @@ export const YoutubeMusicStore = proxyLazyWebpack(() => {
if (message.volume) store.volume = message.volume ?? 0;
if (message.repeat) store.repeat = message.repeat;
store.isSettingPosition = false;
store.emitChange();
});
@ -192,13 +175,9 @@ export const YoutubeMusicStore = proxyLazyWebpack(() => {
? encodeURI("youtubemusic://openVideo " + videoId[1])
: "https://music.youtube.com" + path;
console.log("Open", url);
// https://music.youtube.com/watch?v=BSHYPb15W-Y
VencordNative.native.openExternal(url);
}
set position(p: number) {
this.mPosition = p * 1000;
this.start = Date.now();
@ -212,82 +191,51 @@ export const YoutubeMusicStore = proxyLazyWebpack(() => {
return pos;
}
prev() {
// this.socket.routes.prev();
this.req("post", "/api/v1/previous");
previous() {
if (!this.ensureSocketReady()) return;
this.socket.routes.previous();
}
next() {
// this.socket.routes.next();
this.req("post", "/api/v1/next");
if (!this.ensureSocketReady()) return;
this.socket.routes.next();
}
setVolume(percent: number) {
// this.socket.routes.setVolume(percent);
console.log(Math.floor(percent));
this.req("post", "/api/v1/volume", {
headers: {
"Content-Type": "application/json"
},
body: {
volume: Math.floor(percent)
},
});
if (!this.ensureSocketReady()) return;
this.socket.routes.setVolume(Math.round(percent));
this.volume = percent;
this.emitChange();
}
setPlaying(playing: boolean) {
if (playing) {
// YoutubemusicAPI.routes.pause.actions.do();
this.req("post", "/api/v1/play");
// this.socket.routes.play();
} else {
// YoutubemusicAPI.routes.play.actions.do();
this.req("post", "/api/v1/pause");
// this.socket.routes.pause();
}
if (!this.ensureSocketReady()) return;
this.socket.routes[playing ? "play" : "pause"]();
this.isPlaying = playing;
}
switchRepeat() {
// this.socket.routes.repeat(nextRepeat(this.repeat));
this.req("post", "/api/v1/switch-repeat", {
headers: {
"Content-Type": "application/json"
},
body: {
iteration: 1
}
});
setRepeat(state: Repeat) {
if (!this.ensureSocketReady()) return;
this.socket.routes.repeat(state);
this.repeat = state;
this.emitChange();
}
shuffle() {
// this.socket.routes.shuffle();
this.req("post", "/api/v1/shuffle");
setShuffle(state: boolean) {
if (!this.ensureSocketReady()) return;
this.socket.routes.shuffle();
this.shuffle = state;
this.emitChange();
}
seek(ms: number) {
// this.socket.routes.seek(ms / 1000);
this.req("post", "/api/v1/seek-to", {
headers: {
"Content-Type": "application/json"
},
body: {
seconds: Math.floor(ms / 1000)
}
});
if (!this.ensureSocketReady()) return;
this.socket.routes.seek(Math.round(ms / 1000));
}
private req(method: "post" | "get" | "put", route: string, data: any = {}) {
const { apiServerUrl } = Settings.plugins.YouTubeMusicControls;
if (apiServerUrl === "") return;
console.log(fetch(apiServerUrl + route, {
method,
...data,
...(data.body && { body: JSON.stringify(data.body) })
}));
private ensureSocketReady(): boolean {
if (!this.socket || !this.socket.ready) {
return false;
}
return true;
}
}
const store = new YoutubeMusicStore(FluxDispatcher);
logger.info("Youtube Music Controls initialized");
return store;
});
function restartSocket() {
YoutubeMusicStore.socket.reconnect();
}

View file

@ -82,13 +82,6 @@ const settings = definePluginSettings({
placeholder: "ws://localhost:26539",
default: "ws://localhost:26539",
isValid: isUrlValid,
},
apiServerUrl: {
description: "The api server url",
type: OptionType.STRING,
placeholder: "http://localhost:26538",
default: "http://localhost:26538",
isValid: isUrlValid,
}
});

View file

@ -34,26 +34,26 @@
margin-bottom: 5px;
}
#vc-ytm-progress-bar>[class^="slider"] {
#vc-ytm-progress-bar > [class^="slider"] {
flex-grow: 1;
width: 100%;
padding: 0 !important;
}
#vc-ytm-progress-bar>[class^="slider"] [class^="bar"] {
#vc-ytm-progress-bar > [class^="slider"] [class^="bar"] {
height: 3px !important;
top: calc(12px - 4px / 2 + var(--bar-offset));
}
#vc-ytm-progress-bar>[class^="slider"] [class^="barFill"] {
#vc-ytm-progress-bar > [class^="slider"] [class^="barFill"] {
background-image: var(--vc-ytm-slider-gradient);
}
#vc-ytm-progress-bar>[class^="slider"]:hover [class^="barFill"] {
#vc-ytm-progress-bar > [class^="slider"]:hover [class^="barFill"] {
background-image: var(--vc-ytm-slider-gradient);
}
#vc-ytm-progress-bar>[class^="slider"] [class^="grabber"] {
#vc-ytm-progress-bar > [class^="slider"] [class^="grabber"] {
background-color: var(--vc-ytm-slider-grabber);
width: 12px !important;
height: 12px !important;
@ -65,7 +65,7 @@
transition: opacity 0.1s;
}
#vc-ytm-progress-bar:hover>[class^="slider"] [class^="grabber"] {
#vc-ytm-progress-bar:hover > [class^="slider"] [class^="grabber"] {
opacity: 1;
}
@ -78,8 +78,8 @@
}
.vc-ytm-button-row {
margin-top: 14px;
justify-content: center;
margin-top: 14px;
}
.vc-ytm-button {
@ -93,6 +93,7 @@
display: flex;
justify-content: center;
align-items: center;
position: relative;
}
.vc-ytm-button:hover {
@ -105,8 +106,8 @@
width: 24px;
}
[class*="shuffle"]>svg,
[class*="repeat"]>svg {
[class*="shuffle"] > svg,
[class*="repeat"] > svg {
width: 22px;
height: 22px;
}

View file

@ -167,7 +167,7 @@
margin-bottom: 5px;
}
#vc-spotify-progress-bar>[class^="slider"] {
#vc-spotify-progress-bar > [class^="slider"] {
flex-grow: 1;
width: 100%;
padding: 0 !important;
@ -186,7 +186,7 @@
background-color: var(--vc-spotify-green);
}
#vc-spotify-progress-bar>[class^="slider"] [class^="grabber"] {
#vc-spotify-progress-bar > [class^="slider"] [class^="grabber"] {
/* these importants are necessary, it applies a width and height through inline styles */
height: 16px !important;
width: 16px !important;
@ -199,7 +199,7 @@
transition: opacity 0.1s;
}
#vc-spotify-progress-bar:hover>[class^="slider"] [class^="grabber"] {
#vc-spotify-progress-bar:hover > [class^="slider"] [class^="grabber"] {
opacity: 1;
}
@ -226,4 +226,4 @@
.vc-spotify-fallback {
padding: 0.5em;
color: var(--text-default);
}
}