Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions modules/lyrics-plus/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,7 @@ export const CONFIG = {
providersOrder,
modes: ["karaoke", "synced", "unsynced", "genius"] satisfies LyricMode[],
locked: Number.parseInt(localStorage.getItem("lyrics-plus:lock-mode") || "-1"),
preferredMode: Number.parseInt(localStorage.getItem("lyrics-plus:preferred-mode") || "-1"),
};

export function isProviderKey(value: unknown): value is ProviderKey {
Expand Down
17 changes: 16 additions & 1 deletion modules/lyrics-plus/container-state.test.mts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,8 @@ import "../stdlib/lib/test-setup.mts";
import assert from "node:assert/strict";
import { test } from "node:test";
import { createElement } from "react";
import { translationMode, renderedLines, plainLines, mergeProviderResult } from "./container-state.ts";
import { translationMode, renderedLines, plainLines, mergeProviderResult, pickMode } from "./container-state.ts";
import { GENIUS, KARAOKE, SYNCED, UNSYNCED } from "./config.ts";
import type { DisplayLyricLine, ProviderResult } from "./types.ts";

test("translation modes follow the source language and reject invalid persisted values", () => {
Expand Down Expand Up @@ -75,3 +76,17 @@ test("provider fallback does not overwrite null fields omitted by the next provi
assert.equal(first.neteaseTranslation, null);
assert.equal(Object.hasOwn(first, "mode"), false);
});

test("mode auto-pick prefers the chosen mode and only falls back to karaoke last", () => {
const lines = [{ text: "words", startTime: 0 }];
const all = { karaoke: [{ text: [], startTime: 0 }], synced: lines, unsynced: lines };
assert.equal(pickMode(all, -1), KARAOKE);
assert.equal(pickMode(all, SYNCED), SYNCED);
assert.equal(pickMode(all, UNSYNCED), UNSYNCED);
assert.equal(pickMode({ karaoke: all.karaoke, unsynced: lines }, SYNCED), UNSYNCED);
assert.equal(pickMode({ synced: lines, unsynced: lines }, KARAOKE), SYNCED);
assert.equal(pickMode({ karaoke: all.karaoke }, SYNCED), KARAOKE);
assert.equal(pickMode({ genius: "text" }, SYNCED), GENIUS);
assert.equal(pickMode({ synced: lines }, Number.NaN), SYNCED);
assert.equal(pickMode({}, SYNCED), -1);
});
19 changes: 19 additions & 0 deletions modules/lyrics-plus/container-state.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,9 @@ import {
type LyricLine,
type TranslationMode,
type ProviderResult,
type LyricMode,
} from "./types.ts";
import { CONFIG, GENIUS, KARAOKE, SYNCED, UNSYNCED } from "./config.ts";
import { lyricText, isKaraokeWords } from "./utils.ts";

export function translationMode(
Expand Down Expand Up @@ -77,3 +79,20 @@ export function mergeProviderResult(target: ProviderResult, source: ProviderResu
};
keys.forEach(copy);
}

const AUTO_MODE_ORDER = [KARAOKE, SYNCED, UNSYNCED, GENIUS];

/**
* Pick a mode based on user preference and availability.
*
* Karaoke is only a last resort once another mode is preferred,
* so a Synced/Unsynced or other user chosen mode will never switch to Karaoke on its own
* but Karaoke will still fall back to other modes if lyrics doesn't support Karaoke mode.
*/
export function pickMode(lyrics: Pick<ProviderResult, LyricMode>, preferred: number): number {
const order =
preferred === KARAOKE || !AUTO_MODE_ORDER.includes(preferred)
? AUTO_MODE_ORDER
: [preferred, ...AUTO_MODE_ORDER.filter((mode) => mode !== preferred && mode !== KARAOKE), KARAOKE];
return order.find((mode) => lyrics[CONFIG.modes[mode]]) ?? -1;
}
26 changes: 5 additions & 21 deletions modules/lyrics-plus/mod.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -36,7 +36,7 @@ import {
UNSYNCED,
} from "./config.ts";
import * as UtilsPure from "./utils.ts";
import { translationMode, renderedLines, plainLines, mergeProviderResult } from "./container-state.ts";
import { translationMode, renderedLines, plainLines, mergeProviderResult, pickMode } from "./container-state.ts";
import type { CSSProperties, ReactNode, ReactElement, ChangeEvent } from "react";
import {
type DisplayLyricLine,
Expand Down Expand Up @@ -724,16 +724,7 @@ export class LyricsContainer extends react.Component<LyricsProps, LyricsState> {
} else if (this.state.lockMode !== -1) {
finalMode = this.state.lockMode;
} else {
// Auto switch
if (tempState.karaoke) {
finalMode = KARAOKE;
} else if (tempState.synced) {
finalMode = SYNCED;
} else if (tempState.unsynced) {
finalMode = UNSYNCED;
} else if (tempState.genius) {
finalMode = GENIUS;
}
finalMode = pickMode(tempState, CONFIG.preferredMode);
}
}

Expand Down Expand Up @@ -1319,16 +1310,7 @@ export class LyricsContainer extends react.Component<LyricsProps, LyricsState> {
} else if (this.state.lockMode !== -1) {
mode = this.state.lockMode;
} else {
// Auto switch
if (this.state.karaoke) {
mode = KARAOKE;
} else if (this.state.synced) {
mode = SYNCED;
} else if (this.state.unsynced) {
mode = UNSYNCED;
} else if (this.state.genius) {
mode = GENIUS;
}
mode = pickMode(this.state, CONFIG.preferredMode);
}

let activeItem;
Expand Down Expand Up @@ -1552,6 +1534,8 @@ export class LyricsContainer extends react.Component<LyricsProps, LyricsState> {
const info = this.infoFromTrack(client.player.data.item);
if (info?.uri) this.props.queries.rememberMode(info.uri, mode);

CONFIG.preferredMode = mode;
localStorage.setItem("lyrics-plus:preferred-mode", String(mode));
this.setState({ explicitMode: mode });
if (this.state.provider !== "local") this.fetchLyrics(client.player.data.item, mode);
}
Expand Down
Loading