// ==UserScript==
// @name Bilibili直播纯音频模式
// @description 在bilibili直播界面,点击右下角切换直播/音频模式,减少资源占用,方便黑听
// @version 1.0
// @author Li Li
// @match *://live.bilibili.com/*
// @grant none
// @tag bilibili直播 音频播放
// ==/UserScript==
(function() {
'use strict';
let audioOnly = false;
let hlsScriptLoaded = false;
let audioElement = null;
let currentHls = null;
const videoColor = '#00A1D6';
const audioColor = '#27ae60';
const videoSVG = ``;
const audioSVG = ``;
const btn = document.createElement('button');
btn.innerHTML = videoSVG;
btn.style.position = 'fixed';
btn.style.width = '50px';
btn.style.height = '50px';
btn.style.borderRadius = '50%';
btn.style.border = 'none';
btn.style.background = 'transparent';
btn.style.padding = '11px';
btn.style.cursor = 'pointer';
btn.style.display = 'flex';
btn.style.alignItems = 'center';
btn.style.justifyContent = 'center';
btn.style.transition = 'all 0.2s ease';
btn.style.zIndex = 9999;
btn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
// 恢复上次拖拽位置
const savedPos = JSON.parse(localStorage.getItem('bilibili_audio_btn_pos') || '{}');
if(savedPos.left && savedPos.top){
btn.style.left = savedPos.left;
btn.style.top = savedPos.top;
} else {
btn.style.right = '50px';
btn.style.bottom = '50px';
}
document.body.appendChild(btn);
function setIconColor(color){
const svg = btn.querySelector('svg');
if(svg) svg.setAttribute('fill', color);
}
// 悬浮效果
btn.addEventListener('mouseover', ()=>{
btn.style.background = audioOnly ? audioColor : videoColor;
setIconColor('#FFFFFF');
btn.style.boxShadow = '0 8px 16px rgba(0,0,0,0.4)';
});
btn.addEventListener('mouseout', ()=>{
btn.style.background = 'transparent';
setIconColor(audioOnly ? audioColor : videoColor);
btn.style.boxShadow = '0 4px 8px rgba(0,0,0,0.3)';
});
// 拖拽逻辑
let isDragging = false;
btn.addEventListener('mousedown', e=>{
isDragging = true;
e.preventDefault();
});
document.addEventListener('mousemove', e=>{
if(!isDragging) return;
// 鼠标在按钮中心
let left = e.clientX - btn.offsetWidth/2;
let top = e.clientY - btn.offsetHeight/2;
// 边界限制
left = Math.max(0, Math.min(left, window.innerWidth - btn.offsetWidth));
top = Math.max(0, Math.min(top, window.innerHeight - btn.offsetHeight));
btn.style.left = left + 'px';
btn.style.top = top + 'px';
btn.style.right = 'auto';
btn.style.bottom = 'auto';
});
document.addEventListener('mouseup', ()=>{
if(!isDragging) return;
isDragging = false;
localStorage.setItem('bilibili_audio_btn_pos', JSON.stringify({
left: btn.style.left,
top: btn.style.top
}));
});
btn.addEventListener('click', toggleAudioMode);
async function loadHlsJs(){
if(hlsScriptLoaded) return;
hlsScriptLoaded = true;
return new Promise((resolve,reject)=>{
const script = document.createElement('script');
script.src='https://cdn.jsdelivr.net/npm/hls.js@latest';
script.onload=()=>resolve();
script.onerror=reject;
document.head.appendChild(script);
});
}
async function toggleAudioMode(){
const videos = document.querySelectorAll('video');
if(!audioOnly){
// 切换到音频模式
audioOnly = true;
btn.innerHTML = audioSVG;
setIconColor(audioColor);
videos.forEach(v=>v.style.display='none');
await loadHlsJs();
const src = Array.from(videos).find(v=>v.currentSrc||v.src)?.currentSrc || Array.from(videos).find(v=>v.src)?.src;
if(!src) return;
if(!audioElement){
audioElement = document.createElement('audio');
audioElement.autoplay = true;
audioElement.controls = false;
audioElement.style.display='none';
document.body.appendChild(audioElement);
if(Hls.isSupported()){
currentHls = new Hls({autoStartLoad:true,startLevel:-1,maxBufferLength:30});
currentHls.loadSource(src);
currentHls.attachMedia(audioElement);
currentHls.on(Hls.Events.MANIFEST_PARSED, ()=>{
audioElement.play().catch(()=>{});
});
} else {
audioElement.src = src;
setTimeout(()=>audioElement.play().catch(()=>{}),0);
}
}
} else {
// 切回视频模式
audioOnly = false;
btn.innerHTML = videoSVG;
setIconColor(videoColor);
videos.forEach(v=>v.style.display='');
if(audioElement){
audioElement.pause();
audioElement.src='';
if(currentHls){currentHls.destroy();currentHls=null;}
audioElement.remove();
audioElement=null;
}
}
}
// 观察动态加载的视频
const observer = new MutationObserver(()=>{
if(audioOnly){
document.querySelectorAll('video').forEach(video=>{
if(video.style.display!=='none') toggleAudioMode();
});
}
});
observer.observe(document.body,{childList:true,subtree:true});
})();