Screenshot 2022-10-19 at 14 23 37

Run the script, give it a bandcamp URL (tracks also work) then as long as the artist has allowed embedding (most of them seem to, I think you have to explicitiy disable it) then you get a floating bandcamp player!

Here are some URLs to have a play with:

  • https://garekdruss.bandcamp.com/album/soft-fascination-2
  • https://mansurbrown.bandcamp.com/album/naqi-vol-1
  • https://surprisechef.bandcamp.com/album/education-recreation
  • https://pyecorneraudio.bandcamp.com/album/social-dissonance

Open bandcamp-album-player in Script Kit

// Name: Bandcamp Album Player
// Description: Stream a bandcamp album
// Author: Josh Davenport-Smith
// Twitter: @jdprts
import "@johnlindquist/kit";
import { WidgetAPI } from "@johnlindquist/kit/types/pro";
const jsdom = await npm('jsdom');
const INITIAL_PLAYER_WIDTH = 500;
const MAX_PLAYER_WIDTH = 740;
const INITIAL_PLAYER_HEIGHT = 120;
const htmlDecode = (input: string) => {
const doc = new jsdom.JSDOM(`<!DOCTYPE html><div>${input}</div>`);
return doc.window.document.querySelector('div').textContent;
}
const getStyles = () => `
:root {
--dragbar-width: 30px;
}
iframe {
position: absolute;
left: var(--dragbar-width);
top: 0;
width: calc(100% - var(--dragbar-width));
height: 100%;
-webkit-app-region: no-drag;
}
#dragbar {
position: absolute;
top: 0;
left: 0;
bottom: 0;
width: var(--dragbar-width);
cursor: grab;
background: #F1F1F1;
}
#dragbar:after {
content: '';
position: absolute;
inset: 6px;
background: url("data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHdpZHRoPSI1IiBoZWlnaHQ9IjUiPgo8cmVjdCB3aWR0aD0iNSIgaGVpZ2h0PSI1IiBmaWxsPSIjRjFGMUYxIj48L3JlY3Q+CjxyZWN0IHdpZHRoPSIyIiBoZWlnaHQ9IjIiIGZpbGw9IiNjY2MiPjwvcmVjdD4KPC9zdmc+");
}
#dragbar:active {
cursor: grabbing;
}
`;
const getScripts = (albumId: number) => `
(() => {
const url = 'https://bandcamp.com/EmbeddedPlayer/album=${albumId}/size=large/bgcol=ffffff/linkcol=0687f5/tracklist=false/artwork=small/transparent=true/';
const iframe = document.createElement('iframe');
iframe.setAttribute('src', url);
iframe.setAttribute('seamless', true);
document.body.appendChild(iframe);
})();
`;
// Obtain the album (or track) URL
const albumUrl = await arg('What is the album URL?');
// Bail if the URL is invalid
if (!albumUrl.includes('bandcamp.com/album') && !albumUrl.includes('bandcamp.com/track')) {
throw new Error('Sorry, I don\'t recognise that URL');
}
// Give feedback that we're metadata for the album
div(md(`
Loading album...
---
Note: Some URLs will not work because of permissions set by the artist
`));
// Grab the bandcamp page for bc-page-properties which stores meta about the album
const pageContents = await fetch(albumUrl).then((res) => res.text());
const bandcampMetaParse = pageContents.match(/<meta name="bc-page-properties".*?content="([^"]+)">/);
await Promise.all([
// Give the user a chance to read the message (some URLs will not work and there's no obious way to tell in advance)
new Promise((resolve) => setTimeout(resolve, 2000)),
// Also wait for the data to load, so we continue when both promises resolve
pageContents,
])
if (!bandcampMetaParse) {
throw new Error('Sorry, I couldn\'t find the album ID');
}
// Parse the meta we found
const bandcampMeta: { item_type?: string, item_id?: number, tralbum_page_version?: number } | undefined = JSON.parse(htmlDecode(bandcampMetaParse[1]));
if (!bandcampMeta?.item_id) {
throw new Error('Sorry, I couldn\'t find the album ID');
}
// Create a widget with the player!
const w: WidgetAPI = await widget(
`
<div id="dragbar"></div>
<style type="text/css">${getStyles()}</style>
<script type="text/javascript">${getScripts(bandcampMeta.item_id)}</script>
`,
{
alwaysOnTop: true,
width: INITIAL_PLAYER_WIDTH,
maxWidth: MAX_PLAYER_WIDTH,
height: INITIAL_PLAYER_HEIGHT,
minHeight: INITIAL_PLAYER_HEIGHT,
maxHeight: INITIAL_PLAYER_HEIGHT,
roundedCorners: false,
thickFrame: false,
}
);
w.setSize(INITIAL_PLAYER_WIDTH, INITIAL_PLAYER_HEIGHT);