feat: sync volume icons

This commit is contained in:
trafficlunar 2026-03-20 23:18:07 +00:00
parent 2cdc4a92d4
commit 43eb8ff31f

View file

@ -27,6 +27,13 @@ const BITRATE_MAP: Record<string, string> = {
low: "128000",
};
const VOLUME_ICONS: Record<string, string> = {
high: `<path d="M9.741.85a.75.75 0 0 1 .375.65v13a.75.75 0 0 1-1.125.65l-6.925-4a3.64 3.64 0 0 1-1.33-4.967 3.64 3.64 0 0 1 1.33-1.332l6.925-4a.75.75 0 0 1 .75 0zm-6.924 5.3a2.14 2.14 0 0 0 0 3.7l5.8 3.35V2.8zm8.683 4.29V5.56a2.75 2.75 0 0 1 0 4.88"></path><path d="M11.5 13.614a5.752 5.752 0 0 0 0-11.228v1.55a4.252 4.252 0 0 1 0 8.127z"></path>`,
medium: `<path d="M9.741.85a.75.75 0 0 1 .375.65v13a.75.75 0 0 1-1.125.65l-6.925-4a3.64 3.64 0 0 1-1.33-4.967 3.64 3.64 0 0 1 1.33-1.332l6.925-4a.75.75 0 0 1 .75 0zm-6.924 5.3a2.14 2.14 0 0 0 0 3.7l5.8 3.35V2.8zm8.683 6.087a4.502 4.502 0 0 0 0-8.474v1.65a3 3 0 0 1 0 5.175z"></path>`,
low: `<path d="M9.741.85a.75.75 0 0 1 .375.65v13a.75.75 0 0 1-1.125.65l-6.925-4a3.64 3.64 0 0 1-1.33-4.967 3.64 3.64 0 0 1 1.33-1.332l6.925-4a.75.75 0 0 1 .75 0zm-6.924 5.3a2.14 2.14 0 0 0 0 3.7l5.8 3.35V2.8zm8.683 4.29V5.56a2.75 2.75 0 0 1 0 4.88"></path>`,
muted: `<path d="M13.86 5.47a.75.75 0 0 0-1.061 0l-1.47 1.47-1.47-1.47A.75.75 0 0 0 8.8 6.53L10.269 8l-1.47 1.47a.75.75 0 1 0 1.06 1.06l1.47-1.47 1.47 1.47a.75.75 0 0 0 1.06-1.06L12.39 8l1.47-1.47a.75.75 0 0 0 0-1.06"></path><path d="M10.116 1.5A.75.75 0 0 0 8.991.85l-6.925 4a3.64 3.64 0 0 0-1.33 4.967 3.64 3.64 0 0 0 1.33 1.332l6.925 4a.75.75 0 0 0 1.125-.649v-1.906a4.7 4.7 0 0 1-1.5-.694v1.3L2.817 9.852a2.14 2.14 0 0 1-.781-2.92c.187-.324.456-.594.78-.782l5.8-3.35v1.3c.45-.313.956-.55 1.5-.694z"></path>`,
};
// Stop Jellfin audio
export function stop() {
hijackActive.set(false);
@ -117,7 +124,7 @@ export function registerEvents() {
const results = new Fuse(list, {
keys: ["name", "artists"],
threshold: 0.7,
threshold: 0.75,
}).search(`${trackName} ${artists}`);
console.debug(`[Jellyfin]: Query is "${trackName} ${artists}"`);
@ -188,6 +195,7 @@ export function registerEvents() {
oldTime = event.data;
});
const volumeIcon: SVGElement | null = document.querySelector(".volume-bar__icon-button > span > svg");
const volumeSlider: HTMLDivElement | null = document.querySelector(".volume-bar__slider-container > div > div");
const volumeSliderInput: HTMLInputElement | null = document.querySelector(".volume-bar__slider-container > div > label > input");
@ -203,6 +211,7 @@ export function registerEvents() {
audio.volume = Math.pow(currentVolume, 3) * 0.425;
if (volumeSlider) volumeSlider.style.setProperty("--progress-bar-transform", `${currentVolume * 100}%`);
if (volumeSliderInput) volumeSliderInput.value = currentVolume.toString();
if (volumeIcon) volumeIcon.innerHTML = VOLUME_ICONS[getExpectedVolumeIcon()];
return;
}
return Reflect.apply(target, thisArg, args);
@ -210,31 +219,63 @@ export function registerEvents() {
});
// Spotify tries to set the volume on the slider to 0 when hijacked, this tries to revert it
if (!volumeSlider) return;
const observer = new MutationObserver(() => {
const transform = volumeSlider.style.getPropertyValue("--progress-bar-transform");
if (volumeSlider) {
const observer = new MutationObserver(() => {
if (!hijackActive.get()) return;
const transform = volumeSlider.style.getPropertyValue("--progress-bar-transform");
const currentPercent = currentVolume * 100;
const transformPercent = parseFloat(transform); // strips the "%"
const currentPercent = currentVolume * 100;
const transformPercent = parseFloat(transform); // strips the "%"
// 0.1% tolerance (floating point)
if (Math.abs(currentPercent - transformPercent) > 0.1) {
observer.disconnect(); // prevent re-triggering while we update
volumeSlider.style.setProperty("--progress-bar-transform", `${currentPercent}%`);
observer.observe(volumeSlider, { attributes: true, attributeFilter: ["style"] });
}
});
observer.observe(volumeSlider, { attributes: true, attributeFilter: ["style"] });
// 0.1% tolerance (floating point)
if (Math.abs(currentPercent - transformPercent) > 0.1) {
observer.disconnect(); // prevent re-triggering while we update
volumeSlider.style.setProperty("--progress-bar-transform", `${currentPercent}%`);
observer.observe(volumeSlider, { attributes: true, attributeFilter: ["style"] });
}
});
observer.observe(volumeSlider, { attributes: true, attributeFilter: ["style"] });
}
// Similar to the other observer, but for the input (you'll notice it when scrolling the volume slider)
if (!volumeSliderInput) return;
const inputObserver = new MutationObserver(() => {
// 0.1% tolerance (floating point)
if (Math.abs(currentVolume - volumeSliderInput.valueAsNumber) > 0.1) {
inputObserver.disconnect(); // prevent re-triggering while we update
volumeSliderInput.value = currentVolume.toString();
inputObserver.observe(volumeSlider, { attributes: true, attributeFilter: ["value"] });
}
});
inputObserver.observe(volumeSlider, { attributes: true, attributeFilter: ["value"] });
if (volumeSliderInput) {
const inputObserver = new MutationObserver(() => {
if (!hijackActive.get()) return;
// 0.1% tolerance (floating point)
if (Math.abs(currentVolume - volumeSliderInput.valueAsNumber) > 0.1) {
inputObserver.disconnect(); // prevent re-triggering while we update
volumeSliderInput.value = currentVolume.toString();
inputObserver.observe(volumeSliderInput, { attributes: true, attributeFilter: ["value"] });
}
});
inputObserver.observe(volumeSliderInput, { attributes: true, attributeFilter: ["value"] });
}
// Similar, but for volume icon (tries to show up as muted)
if (volumeIcon) {
let currentIcon = "";
const observer = new MutationObserver(() => {
if (!hijackActive.get()) return;
const expectedIcon = getExpectedVolumeIcon();
if (currentIcon === expectedIcon) return;
observer.disconnect(); // prevent re-triggering while we update
currentIcon = expectedIcon;
volumeIcon.innerHTML = VOLUME_ICONS[getExpectedVolumeIcon()];
observer.observe(volumeIcon, { childList: true, subtree: true });
});
observer.observe(volumeIcon, { childList: true, subtree: true });
}
}
function getExpectedVolumeIcon(): string {
if (currentVolume >= 0.66) return "high";
if (currentVolume >= 0.33) return "medium";
if (currentVolume !== 0) return "low";
return "muted";
}