Upload files to "/"
This commit is contained in:
commit
4f0392ac29
1 changed files with 152 additions and 0 deletions
152
VCSounds.js
Normal file
152
VCSounds.js
Normal 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();
|
||||||
|
|
||||||
|
})();
|
||||||
Loading…
Add table
Reference in a new issue