mirror of
https://github.com/trafficlunar/jellyfin-spicetify.git
synced 2026-06-13 19:07:06 +00:00
fix: fuzzy search tracks
This commit is contained in:
parent
6196e95e30
commit
f8664bfea8
4 changed files with 45 additions and 21 deletions
3
bun.lock
3
bun.lock
|
|
@ -6,6 +6,7 @@
|
||||||
"name": "jellyfin-spicetify",
|
"name": "jellyfin-spicetify",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jellyfin/sdk": "^0.13.0",
|
"@jellyfin/sdk": "^0.13.0",
|
||||||
|
"fuse.js": "^7.1.0",
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/node": "^25.3.3",
|
"@types/node": "^25.3.3",
|
||||||
|
|
@ -111,6 +112,8 @@
|
||||||
|
|
||||||
"function-bind": ["function-bind@1.1.2", "", {}, "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA=="],
|
"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-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=="],
|
"get-proto": ["get-proto@1.0.1", "", { "dependencies": { "dunder-proto": "^1.0.1", "es-object-atoms": "^1.0.0" } }, "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g=="],
|
||||||
|
|
|
||||||
|
|
@ -17,6 +17,7 @@
|
||||||
"esbuild-css-modules-plugin": "^3.1.5"
|
"esbuild-css-modules-plugin": "^3.1.5"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@jellyfin/sdk": "^0.13.0"
|
"@jellyfin/sdk": "^0.13.0",
|
||||||
|
"fuse.js": "^7.1.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -40,9 +40,7 @@ async function main() {
|
||||||
icon,
|
icon,
|
||||||
(self) => {
|
(self) => {
|
||||||
if (self.active) {
|
if (self.active) {
|
||||||
player.hijackActive.set(false);
|
player.stop();
|
||||||
player.audio.pause();
|
|
||||||
Spicetify.Player.setVolume(player.currentVolume);
|
|
||||||
} else {
|
} else {
|
||||||
const oldVolume = player.currentVolume;
|
const oldVolume = player.currentVolume;
|
||||||
Spicetify.Player.setVolume(0); // Set Spotify audio volume
|
Spicetify.Player.setVolume(0); // Set Spotify audio volume
|
||||||
|
|
|
||||||
|
|
@ -1,6 +1,9 @@
|
||||||
import { getSearchApi } from "@jellyfin/sdk/lib/utils/api/search-api";
|
import { getSearchApi } from "@jellyfin/sdk/lib/utils/api/search-api";
|
||||||
import { getPlaystateApi } from "@jellyfin/sdk/lib/utils/api/playstate-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 * as jellyfin from "./jellyfin";
|
||||||
import { settings } from "./settingsStore";
|
import { settings } from "./settingsStore";
|
||||||
import { signal } from "./utils";
|
import { signal } from "./utils";
|
||||||
|
|
@ -19,11 +22,11 @@ const BITRATE_MAP: Record<string, string> = {
|
||||||
low: "128000",
|
low: "128000",
|
||||||
};
|
};
|
||||||
|
|
||||||
export function jellyfinToLocalUri(trackInfo: SearchHint): string {
|
// Stop Jellfin audio
|
||||||
const encode = (s: string) => encodeURIComponent(s ?? "").replace(/%20/g, "+");
|
export function stop() {
|
||||||
const durationSecs = trackInfo.RunTimeTicks ? Math.floor(trackInfo.RunTimeTicks / 10000000) : 0;
|
hijackActive.set(false);
|
||||||
|
audio.pause();
|
||||||
return `spotify:local:${encode(trackInfo.Artists?.[0] ?? "Unknown artist")}:${trackInfo.Id}:${encode(trackInfo.Name ?? "Unknown title")}:${durationSecs}`;
|
Spicetify.Player.setVolume(currentVolume);
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function playTrack(id: string) {
|
export async function playTrack(id: string) {
|
||||||
|
|
@ -87,23 +90,40 @@ export function registerEvents() {
|
||||||
currentItemId = null;
|
currentItemId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
const results = await getSearchApi(jellyfin.api).getSearchHints({
|
const trackName = event.data.item.name;
|
||||||
searchTerm: event.data.item.name,
|
const searchResults = await getSearchApi(jellyfin.api).getSearchHints({
|
||||||
|
searchTerm: trackName,
|
||||||
includeItemTypes: [BaseItemKind.Audio],
|
includeItemTypes: [BaseItemKind.Audio],
|
||||||
limit: 1,
|
limit: 32,
|
||||||
});
|
});
|
||||||
|
|
||||||
const item = results.data.SearchHints?.[0];
|
if (!searchResults.data.SearchHints || searchResults.data.SearchHints.length === 0) {
|
||||||
if (!item?.Id) {
|
stop();
|
||||||
hijackActive.set(false);
|
return;
|
||||||
audio.pause();
|
}
|
||||||
Spicetify.Player.setVolume(currentVolume);
|
|
||||||
|
// 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;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
Spicetify.showNotification("Playing on Jellyfin");
|
Spicetify.showNotification("Playing on Jellyfin");
|
||||||
canUseJellyfin.set(true);
|
canUseJellyfin.set(true);
|
||||||
playTrack(item.Id);
|
playTrack(track.id);
|
||||||
|
|
||||||
audio.currentTime = oldTime; // sync up with Spotify, due to loading times
|
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
|
// onprogress polls every 100ms, small time difference means normal playback
|
||||||
const timeDiff = Math.abs(event.data - oldTime);
|
const timeDiff = Math.abs(event.data - oldTime);
|
||||||
if (Math.abs(timeDiff - 100) < 100) {
|
if (Math.abs(timeDiff - 100) < 200) {
|
||||||
// Allow 100ms tolerance
|
// Allow 200ms tolerance
|
||||||
oldTime = event.data;
|
oldTime = event.data;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log(event.data, oldTime);
|
||||||
|
|
||||||
audio.currentTime = event.data / 1000;
|
audio.currentTime = event.data / 1000;
|
||||||
oldTime = event.data;
|
oldTime = event.data;
|
||||||
});
|
});
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue