Skip to content

Instantly share code, notes, and snippets.

@Alloyed
Last active March 24, 2024 20:56
Show Gist options
  • Save Alloyed/900e4ba569bdc8728e4c86403f77b249 to your computer and use it in GitHub Desktop.
Save Alloyed/900e4ba569bdc8728e4c86403f77b249 to your computer and use it in GitHub Desktop.
Embeds a whole winamp instance on your cohost feed, when audio embeds are detected. All audio files on the page are added to a playlist and can be queued one after the other, or shuffled. (+any other winamp features like eq/panning)
// ==UserScript==
// @name Winamp on Cohost
// @namespace Violentmonkey Scripts
// @match https://cohost.org/*
// @noframes
// @require https://unpkg.com/webamp
// @grant GM.addElement
// @version 0.2
// @author https://cohost.org/alloyed
// @description 3/9/2024, 6:06:50 PM: Embeds a whole winamp instance on your cohost feed, when audio embeds are detected. All audio files on the page are added to a playlist and can be queued one after the other, or shuffled. (+any other winamp features like eq/panning)
// @license MIT
// ==/UserScript==
// state variables
let sWebamp = null;
let sWebampReady = false;
const sTracksToAdd = [];
const sDiscoveredTracks = new Set();
function debounce(func, timeoutMs) {
timeoutMs = timeoutMs || 16;
let timer;
return (...args) => {
clearTimeout(timer);
timer = setTimeout(() => { func.apply(this, args); }, timeoutMs);
};
}
function retrieveTracksFromAudioTags() {
const tracks = [];
const audioNodes = document.querySelectorAll('audio');
audioNodes.forEach((node)=> {
// From screen reader data, retrieve artist and title
let artist = (node.parentNode.querySelector('figcaption').firstChild.textContent);
if (artist === "(unknown artist)") {
artist = undefined; // TODO: get artist name from cohost profile?
}
const title = (node.parentNode.querySelector('figcaption').lastChild.textContent);
tracks.push({
url: node.src,
metaData: {
title: title,
artist: artist,
},
});
});
return tracks;
}
function updateWebamp()
{
if(sWebamp) {
if(sWebampReady) {
sWebamp.appendTracks(sTracksToAdd);
sTracksToAdd.length = 0;
} else {
// webamp is currently rendering, we'll add anything added now when it's ready.
}
} else if (sTracksToAdd.length > 0) {
// we've discovered some tracks, but haven't made a webamp instance yet. let's do that.
sWebamp = new Webamp({
enableDoubleSizeMode: true, // TODO: doesn't seem to work
});
sWebamp.renderWhenReady(GM.addElement(document.body, 'div', {id: 'webamp_startingLocation', style: 'position: absolute; right: 80px; top: 300px; width: 100px; height: 100px;'})).then(async() => {
// keep all webamp elements on screen
document.querySelector("#main-window").parentNode.parentNode.parentNode.style.position = 'fixed';
// add all queued tracks
sWebampReady = true;
updateWebamp();
})
}
}
const discoverNewTracks = debounce(function () {
const allTracks = retrieveTracksFromAudioTags();
for(const track of allTracks) {
if(!sDiscoveredTracks.has(track.url)) {
sTracksToAdd.push(track);
sDiscoveredTracks.add(track.url);
}
}
updateWebamp();
})
// Yes this triggers _any_ time the dom changes in the entire app. I couldn't find a better way to do this so I'll just debounce the results
new MutationObserver(() => {
discoverNewTracks();
}).observe(document.querySelector('#app'), {subtree: true, childList: true});
discoverNewTracks();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment