Adds a volume bar to Bandcamp, styled with the album page's accent color, auto-inverts icons on dark backgrounds.
// ==UserScript==
// @name Bandcamp Volume Bar 2.0
// @version 3.0
// @description Adds a volume bar to Bandcamp, styled with the album page's accent color, auto-inverts icons on dark backgrounds.
// @author @nj4442
// @match *://*.bandcamp.com/*
// @exclude *://bandcamp.com/
// @license MIT
// @grant GM_addStyle
// @require http://ajax.googleapis.com/ajax/libs/jquery/1.11.3/jquery.js
// @namespace https://greasyfork.org/en/users/1490367-nj4442
// ==/UserScript==
(function () {
'use strict';
/* ------------------ COLOR HELPERS ------------------ */
function rgbToHex(r, g, b) {
return "#" + ((1 << 24) + (r << 16) + (g << 8) + b)
.toString(16)
.slice(1);
}
function isDarkColor(hex) {
if (hex.length === 4) {
hex = "#" + [...hex.slice(1)].map(c => c + c).join("");
}
const r = parseInt(hex.substr(1, 2), 16);
const g = parseInt(hex.substr(3, 2), 16);
const b = parseInt(hex.substr(5, 2), 16);
const brightness = (r * 299 + g * 587 + b * 114) / 1000;
return brightness < 128;
}
function getElementBgColor(el) {
if (!el) return '#ffffff';
const bg = getComputedStyle(el).backgroundColor;
if (bg.startsWith('rgb')) {
const rgb = bg.match(/\d+/g);
return rgbToHex(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2]));
}
return bg;
}
/* ------------------ ACCENT COLOR ------------------ */
function getAccentColor() {
const accentElem = document.querySelector('.primaryText, .compound-button');
if (accentElem) {
const color = getComputedStyle(accentElem).color || '';
if (color.startsWith('rgb')) {
const rgb = color.match(/\d+/g);
if (rgb && rgb.length >= 3) {
return rgbToHex(parseInt(rgb[0]), parseInt(rgb[1]), parseInt(rgb[2]));
}
}
return color;
}
return '#f2a6ea'; // fallback
}
/* ------------------ INITIAL SETUP ------------------ */
const accentColor = getAccentColor();
let percentage = 75;
let muted = false;
const speakerUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/2/21/Speaker_Icon.svg/250px-Speaker_Icon.svg.png";
const muteUrl = "https://upload.wikimedia.org/wikipedia/commons/thumb/3/3f/Mute_Icon.svg/250px-Mute_Icon.svg.png";
const pgBdBgColor = getElementBgColor(document.querySelector('#pgBd'));
const darkBg = isDarkColor(pgBdBgColor);
/* ------------------ DOM INSERTION ------------------ */
$("audio").attr("id", "audioSource");
const $control = $("<div class='volumeControl'></div>").insertAfter(".inline_player");
$control.append("<div class='speaker'></div>");
$control.append("<div class='volume'><span class='volumeInner'></span></div>");
/* ------------------ STYLING ------------------ */
let css = `
.volumeControl {
margin-bottom: 10px;
display: flex;
align-items: center;
gap: 10px;
}
.speaker {
width: 30px;
height: 30px;
background: url('${speakerUrl}') center/contain no-repeat;
cursor: pointer;
${darkBg ? 'filter: invert(1);' : ''}
}
.volume {
position: relative;
flex: 1;
height: 10px;
background-color: rgba(0, 0, 0, 0.1);
border-radius: 5px;
overflow: hidden;
cursor: pointer;
}
.volumeInner {
position: absolute;
top: 0;
left: 0;
height: 100%;
width: ${percentage}%;
background-color: ${accentColor};
border-radius: 5px;
}
`;
GM_addStyle(css);
/* ------------------ VOLUME LOGIC ------------------ */
$("#audioSource")[0].volume = percentage / 100;
function changeVolume(e) {
const offset = $(".volume").offset().left;
const width = $(".volume").width();
let pos = (e.pageX - offset) / width;
pos = Math.max(0, Math.min(1, pos));
percentage = Math.floor(pos * 100);
$(".volumeInner").css("width", `${percentage}%`);
$("#audioSource")[0].volume = pos;
if (muted) toggleMute(); // unmute if volume changed
}
function toggleMute() {
muted = !muted;
if (muted) {
$(".speaker").css("background-image", `url('${muteUrl}')`);
$("#audioSource")[0].volume = 0;
$(".volumeInner").css("width", "0%");
} else {
$(".speaker").css("background-image", `url('${speakerUrl}')`);
$("#audioSource")[0].volume = percentage / 100;
$(".volumeInner").css("width", `${percentage}%`);
}
}
/* ------------------ EVENT HANDLERS ------------------ */
$(".volume").mousedown(e => {
changeVolume(e);
$("body").on("mousemove.volume", changeVolume);
$("body").css("user-select", "none");
});
$(document).mouseup(() => {
$("body").off("mousemove.volume");
$("body").css("user-select", "auto");
});
$(".speaker").click(toggleMute);
})();