Roll20 is a great tool for running tabletop games online. One of its features is the ability to play audio that is synchronized across all players in the game. Excellent for setting the mood!
Unfortunately, the built-in audio controls are a bit clunky to mess around with, especially when you’re trying to keep all the various plates of a complex RPG spinning. I had previously been using an API script called Roll20 Audio Master but wanted something a bit more reliable, and if I could make it work without requiring a Pro account, all the better.
The solution?
Bookmarklets!
What are bookmarklets? They’re little bits of bookmarkable JavaScript code which runs when you click the link. Think of them like tiny browser extensions, complete with all the same security concerns, but these are very simple, carefully targeted, thoroughly tested, and totally safe. But don’t take my word for it (seriously), read on and understand what they’re doing. I’ll explain the code in a moment, but first, here they are, in all their drag-and-drop glory:
- Play Playlist – Click and drag that link into your bookmark bar, then edit it and replace PLAYLIST NAME with the playlist you want to play.
- Play Track – Here’s one that plays a specific track. Same deal, just replace TRACK NAME with (shocker) the name of your track.
- Stop All – This one will stop all currently-playing tracks. No editing necessary.
Note: The playlist/track bookmarklets will play the first playlist/track that includes whatever you replace PLAYLIST/TRACK NAME with in its name. So if you find it’s playing the wrong track or playlist, you might need to rename them to make them more unique!
Organization and Invocation
I keep all my Roll20 audio bookmarklets in a folder in the first slot in my bookmark bar, that way I can use SuperMacros like this one to activate them:
{{alt}{shift}{b}}{{down}}{{enter}}
(Repeat {{down}}
as many times as needed to select the desired item in the folder.)
I have a bunch of those macros on my Stream Deck XL, which I wrote a whole separate post about using with Roll20.
Explanation
Because they’re squeezed into links, bookmarklets can be a bit hard to read, but I’ll try my best to break these down. Let’s take a look at each and see what they’re doing.
Here’s the content of the playlist bookmarklet:
javascript:$("#jukeboxfolderroot .dd-folder .folder-title:contains(PLAYLIST NAME)").next().find('.play').click()
Let’s look at that bit by bit:
Code | Explanation |
javascript: | This prefix tells your web browser to treat the rest of the URL as JavaScript code. |
$(" | This is the beginning of a jQuery selector, which is used to find a specific element on the page. Specifically… |
#jukeboxfolderroot | we’re looking for an element with this ID… |
.dd-folder | which contains an element with this class… |
.folder-title | which contains an element with this class… |
:contains(PLAYLIST NAME) | which contains this text (the part you customize). |
") | That’s the end of the selector. |
.next() | Now we’re going to take that title element and find the next element (its sibling), which contains the play controls for that playlist. |
.find(".play") | This is another selector inside the scope of the play controls container. It finds the play button. Now all that’s left to do is… |
.click() | Click it! This is basically the same as if you clicked on the button yourself. |
Due to the way Roll20 was built, these elements are always loaded even if that tab is not visible, so this works even if you’re on a different tab. Very convenient!
Compare and contrast the play track bookmarklet:
javascript:$("#jukeboxfolderroot .jukeboxitem .title:contains(TRACK NAME)").next().find('.play').click()
As you can see, it’s very similar. The only difference is the selector, which finds a track item instead of a playlist.
The stop all bookmarklet is a little different:
javascript:var p;do{p=$("#jukeboxwhatsplaying .play");p.click()}while(p.length)
This should be easier to understand if we format it more like normal JavaScript code:
var p;
do {
p = $("#jukeboxwhatsplaying .play");
p.click()
} while (p.length)
Here’s what that’s doing:
Code | Explanation |
var p; | Declare a variable called p |
do { | Repeat the code in this block until the while-condition stops being true. |
p = $("#jukeboxwhatsplaying .play"); | Use jQuery to find a play button for a currently playing item. Store it in p |
p.click() | Click the button (p ) to stop playback |
} while (p.length) | Keep doing this until p.length (the number of play buttons found) is a false value (0). |