diff --git a/bun.lock b/bun.lock index 5a0cb4d..ae6335e 100644 --- a/bun.lock +++ b/bun.lock @@ -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=="], diff --git a/package.json b/package.json index 58e986a..b0d0e00 100644 --- a/package.json +++ b/package.json @@ -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" } } diff --git a/src/main.ts b/src/main.ts index 667fd79..8de688f 100644 --- a/src/main.ts +++ b/src/main.ts @@ -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 diff --git a/src/player.ts b/src/player.ts index f06ce86..958a113 100644 --- a/src/player.ts +++ b/src/player.ts @@ -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 = { 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; });