fix: fuzzy search tracks

This commit is contained in:
trafficlunar 2026-03-12 22:15:26 +00:00
parent 6196e95e30
commit f8664bfea8
4 changed files with 45 additions and 21 deletions

View file

@ -6,6 +6,7 @@
"name": "jellyfin-spicetify",
"dependencies": {
"@jellyfin/sdk": "^0.13.0",
"fuse.js": "^7.1.0",
},
"devDependencies": {
"@types/node": "^25.3.3",
@ -111,6 +112,8 @@
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
"fuse.js": ["fuse.js@7.1.0", "", {}, "sha512-trLf4SzuuUxfusZADLINj+dE8clK1frKdmqiJNb1Es75fmI5oY6X2mxLVUciLLjxqw/xr72Dhy+lER6dGd02FQ=="],
"get-intrinsic": ["get-intrinsic@1.3.0", "", { "dependencies": { "call-bind-apply-helpers": "^1.0.2", "es-define-property": "^1.0.1", "es-errors": "^1.3.0", "es-object-atoms": "^1.1.1", "function-bind": "^1.1.2", "get-proto": "^1.0.1", "gopd": "^1.2.0", "has-symbols": "^1.1.0", "hasown": "^2.0.2", "math-intrinsics": "^1.1.0" } }, "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ=="],
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],

View file

@ -17,6 +17,7 @@
"esbuild-css-modules-plugin": "^3.1.5"
},
"dependencies": {
"@jellyfin/sdk": "^0.13.0"
"@jellyfin/sdk": "^0.13.0",
"fuse.js": "^7.1.0"
}
}

View file

@ -40,9 +40,7 @@ async function main() {
icon,
(self) => {
if (self.active) {
player.hijackActive.set(false);
player.audio.pause();
Spicetify.Player.setVolume(player.currentVolume);
player.stop();
} else {
const oldVolume = player.currentVolume;
Spicetify.Player.setVolume(0); // Set Spotify audio volume

View file

@ -1,6 +1,9 @@
import { getSearchApi } from "@jellyfin/sdk/lib/utils/api/search-api";
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api/playstate-api";
import { BaseItemKind, SearchHint } from "@jellyfin/sdk/lib/generated-client/models";
import { BaseItemKind } from "@jellyfin/sdk/lib/generated-client/models";
import Fuse from "fuse.js";
import * as jellyfin from "./jellyfin";
import { settings } from "./settingsStore";
import { signal } from "./utils";
@ -19,11 +22,11 @@ const BITRATE_MAP: Record<string, string> = {
low: "128000",
};
export function jellyfinToLocalUri(trackInfo: SearchHint): string {
const encode = (s: string) => encodeURIComponent(s ?? "").replace(/%20/g, "+");
const durationSecs = trackInfo.RunTimeTicks ? Math.floor(trackInfo.RunTimeTicks / 10000000) : 0;
return `spotify:local:${encode(trackInfo.Artists?.[0] ?? "Unknown artist")}:${trackInfo.Id}:${encode(trackInfo.Name ?? "Unknown title")}:${durationSecs}`;
// Stop Jellfin audio
export function stop() {
hijackActive.set(false);
audio.pause();
Spicetify.Player.setVolume(currentVolume);
}
export async function playTrack(id: string) {
@ -87,23 +90,40 @@ export function registerEvents() {
currentItemId = null;
}
const results = await getSearchApi(jellyfin.api).getSearchHints({
searchTerm: event.data.item.name,
const trackName = event.data.item.name;
const searchResults = await getSearchApi(jellyfin.api).getSearchHints({
searchTerm: trackName,
includeItemTypes: [BaseItemKind.Audio],
limit: 1,
limit: 32,
});
const item = results.data.SearchHints?.[0];
if (!item?.Id) {
hijackActive.set(false);
audio.pause();
Spicetify.Player.setVolume(currentVolume);
if (!searchResults.data.SearchHints || searchResults.data.SearchHints.length === 0) {
stop();
return;
}
// Fuzzy search
const artists = event.data.item.artists?.map((a) => a.name).join(" ") ?? "";
const list = searchResults.data.SearchHints.map((v) => ({
id: v.Id ?? "",
name: v.Name ?? "Unknown title",
artists: (v.Artists ?? ["Unknown artist"]).join(" "),
}));
const results = new Fuse(list, {
keys: ["name", "artists"],
threshold: 0.5,
}).search(`${trackName} ${artists}`);
const track = results[0]?.item;
if (!track) {
stop();
return;
}
Spicetify.showNotification("Playing on Jellyfin");
canUseJellyfin.set(true);
playTrack(item.Id);
playTrack(track.id);
audio.currentTime = oldTime; // sync up with Spotify, due to loading times
});
@ -147,12 +167,14 @@ export function registerEvents() {
// onprogress polls every 100ms, small time difference means normal playback
const timeDiff = Math.abs(event.data - oldTime);
if (Math.abs(timeDiff - 100) < 100) {
// Allow 100ms tolerance
if (Math.abs(timeDiff - 100) < 200) {
// Allow 200ms tolerance
oldTime = event.data;
return;
}
console.log(event.data, oldTime);
audio.currentTime = event.data / 1000;
oldTime = event.data;
});