// ==UserScript== // @name Music Room // @namespace music-room-bgm // @version 1.4.0 // @author CHANG // @description 一起在bgm听bgm吧! // @match https://bgm.tv/ // @match https://bangumi.tv/ // @match https://chii.in/ // @grant none // @license MIT // @downloadURL none // ==/UserScript== (function () { 'use strict'; const WORKER_WS = "wss://music-room.mikuorz.workers.dev/room/default"; const style = document.createElement("style"); style.innerHTML = ` #music-room-panel { position: fixed; top: 100px; right: 20px; width: 320px; background: rgba(17, 17, 17, 0.98); color: #eee; border-radius: 12px; font-family: sans-serif; z-index: 999999; box-shadow: 0 10px 30px rgba(0,0,0,0.5); border: 1px solid #333; display: flex; flex-direction: column; overflow: hidden; transition: width 0.3s; } #music-room-panel.minimized { width: 45px; height: 45px; border-radius: 50%; overflow: hidden; } #room-header { background: #222; padding: 10px 15px; cursor: move; display: flex; justify-content: space-between; align-items: center; border-bottom: 1px solid #333; user-select: none; } .header-btns span { margin-left: 10px; cursor: pointer; font-family: monospace; font-size: 16px; color: #888; } .header-btns span:hover { color: #fff; } .room-content { padding: 15px; } #statusTag { font-size: 10px; padding: 2px 6px; border-radius: 4px; background: #333; color: #7fd; } #current-title { font-size: 15px; font-weight: bold; margin: 10px 0 5px; color: #fff; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; } #playlist-container { max-height: 100px; overflow-y: auto; font-size: 12px; color: #999; margin: 10px 0; border-top: 1px solid #222; padding-top: 5px; } .song-item { padding: 3px 0; border-bottom: 1px solid #222; } .input-group { display: flex; flex-direction: column; gap: 6px; margin-top: 10px; } .input-group input { background: #111; border: 1px solid #444; color: #fff; padding: 6px; border-radius: 4px; font-size: 12px; } .input-group button { background: #1db954; border: none; color: #fff; padding: 8px; border-radius: 4px; cursor: pointer; font-weight: bold; } #room-audio { width: 100%; height: 35px; filter: invert(90%) hue-rotate(180deg); margin-top: 10px; } #skip-link { font-size: 11px; color: #666; text-align: center; margin: 8px 0; cursor: pointer; text-decoration: underline; } #mini-icon { display: none; width: 100%; height: 100%; justify-content: center; align-items: center; font-size: 20px; cursor: pointer; background: #1db954; } #music-room-panel.minimized .room-content, #music-room-panel.minimized #room-header { display: none; } #music-room-panel.minimized #mini-icon { display: flex; } `; document.head.appendChild(style); const panel = document.createElement("div"); panel.id = "music-room-panel"; panel.innerHTML = `
🎵
🎵 Music Room
CONNECTING 0人
...
00:00 / 00:00
`; document.body.appendChild(panel); const audio = document.getElementById("room-audio"); const titleText = document.getElementById("current-title"); const timeText = document.getElementById("time-info"); const statusTag = document.getElementById("statusTag"); const listContainer = document.getElementById("playlist-container"); // 窗口控制逻辑 document.getElementById("btn-min").onclick = (e) => { e.stopPropagation(); panel.classList.add("minimized"); }; document.getElementById("mini-icon").onclick = () => panel.classList.remove("minimized"); document.getElementById("btn-close").onclick = () => { if(confirm("确定要退出播放间吗?")) panel.style.display = "none"; }; // 拖动逻辑 let isDragging = false, offsetX, offsetY; const header = document.getElementById("room-header"); header.onmousedown = (e) => { isDragging = true; offsetX = e.clientX - panel.offsetLeft; offsetY = e.clientY - panel.offsetTop; }; document.onmousemove = (e) => { if (!isDragging || panel.classList.contains("minimized")) return; panel.style.left = (e.clientX - offsetX) + "px"; panel.style.top = (e.clientY - offsetY) + "px"; panel.style.right = "auto"; }; document.onmouseup = () => { isDragging = false; }; // WebSocket 逻辑 const ws = new WebSocket(WORKER_WS); ws.onmessage = (e) => { const data = JSON.parse(e.data); if (data.type === "state") { if (!data.current) { titleText.textContent = "暂无播放"; listContainer.innerHTML = ""; audio.src = ""; return; } titleText.textContent = data.current.title; listContainer.innerHTML = "队列:"; data.playlist.forEach(s => { const item = document.createElement("div"); item.className = "song-item"; item.textContent = s.title; listContainer.appendChild(item); }); const now = Date.now(); if (now >= data.endsAt) { statusTag.textContent = "BUFFERING"; audio.pause(); return; } statusTag.textContent = "LIVE"; if (audio.src !== data.current.url) { audio.src = data.current.url; audio.load(); } const target = (now - data.startedAt) / 1000; if (Math.abs(audio.currentTime - target) > 2) audio.currentTime = target; if (audio.paused) audio.play().catch(() => statusTag.textContent = "MUTED"); } else if (data.type === "online") { document.getElementById("onlineCount").textContent = `${data.count}人`; } }; // 点歌逻辑 document.getElementById("addSong").onclick = async () => { const tI = document.getElementById("songTitle"), uI = document.getElementById("songUrl"), btn = document.getElementById("addSong"); const url = uI.value.trim(); if(!url) return; btn.disabled = true; btn.textContent = "..."; try { const dur = await new Promise((res, rej) => { const a = new Audio(url); a.onloadedmetadata = () => res(a.duration); a.onerror = rej; setTimeout(rej, 8000); }); ws.send(JSON.stringify({ type: "enqueue", song: { id: Date.now().toString(), title: tI.value || "未命名", url, duration: Math.ceil(dur) } })); tI.value = ""; uI.value = ""; } catch(e) { alert("Error"); } btn.disabled = false; btn.textContent = "点歌"; }; document.getElementById("skip-link").onclick = () => ws.send(JSON.stringify({ type: "skip" })); setInterval(() => { if (audio.src) timeText.textContent = `${Math.floor(audio.currentTime/60)}:${Math.floor(audio.currentTime%60).toString().padStart(2,'0')} / ${Math.floor(audio.duration/60 || 0)}:${Math.floor(audio.duration%60 || 0).toString().padStart(2,'0')}`; }, 1000); })();