Upload files to "/"

This commit is contained in:
ad3laid3 2026-05-04 20:48:57 -04:00
commit 4f0392ac29

152
VCSounds.js Normal file
View file

@ -0,0 +1,152 @@
(function () {
if (window.__VC_SOUNDS__) return;
window.__VC_SOUNDS__ = true;
const ctx = new (window.AudioContext || window.webkitAudioContext)();
document.addEventListener("click", () => {
if (ctx.state === "suspended") ctx.resume();
}, { once: true });
// ─── Synth ────────────────────────────────────────────────────────────────────
// Warm, low, soft — layered sine + triangle with a slow attack and long decay.
// No harsh transients. Frequencies kept in the 200-500hz range.
function playNote(freq, startTime, duration, volume) {
// Main body: sine (pure, smooth)
const osc1 = ctx.createOscillator();
const gain1 = ctx.createGain();
osc1.type = "sine";
osc1.frequency.value = freq;
osc1.connect(gain1);
gain1.connect(ctx.destination);
// Warmth layer: triangle an octave down (adds body without brightness)
const osc2 = ctx.createOscillator();
const gain2 = ctx.createGain();
osc2.type = "triangle";
osc2.frequency.value = freq / 2;
osc2.connect(gain2);
gain2.connect(ctx.destination);
// Soft attack (~30ms), smooth exponential decay
gain1.gain.setValueAtTime(0, startTime);
gain1.gain.linearRampToValueAtTime(volume, startTime + 0.03);
gain1.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
gain2.gain.setValueAtTime(0, startTime);
gain2.gain.linearRampToValueAtTime(volume * 0.4, startTime + 0.03);
gain2.gain.exponentialRampToValueAtTime(0.001, startTime + duration);
osc1.start(startTime); osc1.stop(startTime + duration + 0.05);
osc2.start(startTime); osc2.stop(startTime + duration + 0.05);
}
// Join: two soft ascending notes, low register, gentle interval
function playJoin() {
if (ctx.state === "suspended") ctx.resume();
const t = ctx.currentTime + 0.01;
playNote(294, t, 0.35, 0.14); // D4
playNote(370, t + 0.28, 0.45, 0.11); // F#4
}
// Leave: same notes descending
function playLeave() {
if (ctx.state === "suspended") ctx.resume();
const t = ctx.currentTime + 0.01;
playNote(370, t, 0.35, 0.14); // F#4
playNote(294, t + 0.28, 0.45, 0.11); // D4
}
// ─── State ────────────────────────────────────────────────────────────────────
let inVoice = false;
let initialising = false;
let initTimer = null;
let globalObserver = null;
function onSelfJoined() {
if (inVoice) return;
inVoice = true;
initialising = true;
playJoin();
console.debug("[VCSounds] self joined");
clearTimeout(initTimer);
initTimer = setTimeout(() => { initialising = false; }, 1500);
}
function onSelfLeft() {
if (!inVoice) return;
inVoice = false;
initialising = false;
clearTimeout(initTimer);
playLeave();
console.debug("[VCSounds] self left");
}
// ─── Participant entry detection ──────────────────────────────────────────────
function isParticipantEntry(el) {
if (el.nodeType !== 1) return false;
const c = el.className || "";
return (
c.includes("p_var(--gap-sm)") &&
c.includes("pos_relative") &&
c.includes("d_flex") &&
c.includes("ai_center")
);
}
// ─── Global mutation observer ─────────────────────────────────────────────────
function startObserver() {
if (globalObserver) return;
globalObserver = new MutationObserver((mutations) => {
if (!inVoice || initialising) return;
for (const m of mutations) {
for (const node of m.addedNodes) {
if (isParticipantEntry(node)) {
console.debug("[VCSounds] participant joined");
playJoin();
}
}
for (const node of m.removedNodes) {
if (isParticipantEntry(node)) {
console.debug("[VCSounds] participant left");
playLeave();
}
}
}
});
globalObserver.observe(document.body, { childList: true, subtree: true });
}
// ─── Fetch hook ───────────────────────────────────────────────────────────────
const originalFetch = window.fetch;
window.fetch = async function (...args) {
const url = typeof args[0] === "string" ? args[0] : args[0]?.url ?? "";
const response = await originalFetch.apply(this, args);
if (url.includes("/join_call") && response.ok) {
setTimeout(onSelfJoined, 300);
}
return response;
};
// ─── WebSocket hook ───────────────────────────────────────────────────────────
const OriginalWebSocket = window.WebSocket;
window.WebSocket = function (url, protocols) {
const ws = protocols
? new OriginalWebSocket(url, protocols)
: new OriginalWebSocket(url);
if (typeof url === "string" && url.includes("/livekit/rtc")) {
ws.addEventListener("close", () => onSelfLeft());
}
return ws;
};
Object.assign(window.WebSocket, OriginalWebSocket);
window.WebSocket.prototype = OriginalWebSocket.prototype;
startObserver();
})();