// ==UserScript== // @name 哔哩哔哩画中画弹幕 // @namespace qwq0 // @version 0.19 // @description 哔哩哔哩画中画支持显示弹幕 // @author QwQ~ // @match https://www.bilibili.com/video/* // @match https://www.bilibili.com/medialist/play/* // @match https://www.bilibili.com/bangumi/play/* // @match https://live.bilibili.com/* // @match https://www.acfun.cn/v/* // @icon data:image/gif;base64,R0lGODlhAQABAAAAACH5BAEKAAEALAAAAAABAAEAAAICTAEAOw== // @grant none // @downloadURL none // ==/UserScript== setTimeout(function() { 'use strict'; function setValue(name, value) { if(GM_setValue) GM_setValue(name, value); } function getValue(name, defaultValue) { return (GM_getValue ? GM_getValue(name, defaultValue) : defaultValue); } var videoHolder = null; var video = null; var width = 0; var height = 0; var canvas = document.createElement("canvas"); var context = canvas.getContext("2d"); var canvasWidth = canvas.width = 0; var canvasHeight = canvas.height = 0; var danmuFontsize = 0; var textCanvas = document.createElement("canvas"); var textCanvasContext = textCanvas.getContext("2d"); var nVideo = document.createElement("video"); var timeoutId = 0; if(navigator.userAgent.indexOf("Firefox") > -1) { document.body.appendChild(nVideo); nVideo.style.position = "fixed"; nVideo.style.zIndex = 10000; nVideo.style.left = "0"; nVideo.style.top = "85px"; nVideo.style.width = "30px"; nVideo.style.height = "30px"; nVideo.style.backgroundColor = "rgba(0, 0, 0, 0.3)"; timeoutId = setTimeout(draw, Math.floor(1000 / 60)); } var danmuList = []; var danmuLine = []; var danmuCount = 0; var danmuMaxLine = 12; var danmuHolder = null; async function addDanmu(text, color) { if(timeoutId && (danmuList.length <= 20 || Math.random() < 20 / danmuList.length)) { danmuCount++; var lineNum = 0; for(var i=0;i < 5 && danmuLine[lineNum] + 6 >= danmuCount;i++) { lineNum = Math.floor(Math.random() * danmuMaxLine); } danmuLine[lineNum] = danmuCount; if(!color) color = "rgb(255, 255, 255)"; var textWidth = textCanvasContext.measureText(text).width; textCanvasContext.clearRect(0, 0, textWidth, danmuFontsize); textCanvasContext.fillStyle = color; textCanvasContext.fillText(text, 0, 0); danmuList.push({text: text, color: color, x: canvasWidth, y: lineNum * danmuFontsize, w: textWidth,i: await createImageBitmap(textCanvas, 0, 0, textWidth, danmuFontsize) }); } } var danmuObserver = new MutationObserver(e => { e.forEach(o=>{ // console.log("danmu(all)", o); if(o.type == "childList") { o.addedNodes.forEach(ele =>{ // console.log("danmu(ele)", ele); if(ele.innerText) { let text = ele.innerText; let color = ele.style.color; if(!color) color = ele.style.getPropertyValue("--color"); addDanmu(text, color); console.log("danmu(it)", color, text); } else if(ele.textContent) { let text = ele.textContent; let color = o.target.style.color; if(!color) color = o.target.style.getPropertyValue("--color"); addDanmu(text, color); console.log("danmu(ct)", color, text); } }); } }); }); setInterval(()=>{ var nowVideoHolder = document.getElementsByClassName("bilibili-player-video")[0] || document.getElementsByClassName("bpx-player-video-wrap")[0] || document.getElementById("live-player") || document.getElementsByClassName("container-video")[0]; if(!nowVideoHolder) return; var nowVideo = nowVideoHolder.getElementsByTagName("video")[0]; if(nowVideo && video != nowVideo) { videoHolder = nowVideoHolder; video = nowVideo; video.addEventListener("enterpictureinpicture",() => { if(!timeoutId) { timeoutId = setTimeout(draw, Math.floor(1000 / 60)); setTimeout(()=>{ nVideo.requestPictureInPicture(); nVideo.play(); }, 250); } else { nVideo.requestPictureInPicture(); nVideo.play(); } }); var style = document.createElement("style"); style.innerText = ` .bpx-player-ctrl-btn.bpx-player-ctrl-pip, .bilibili-player-video-btn.bilibili-player-video-btn-pip.closed { filter: drop-shadow(1px 1px 3px #49e3dc); } `; document.body.appendChild(style); if(navigator.mediaSession) { try { navigator.mediaSession.setActionHandler("play", ()=>{ video.play(); nVideo.play(); }); navigator.mediaSession.setActionHandler("pause", ()=>{ video.pause(); nVideo.pause(); }); } catch(err) { console.warn("[哔哩哔哩画中画弹幕]", "绑定媒体功能键时发生错误"); } } } var nowDanmuHolder = document.getElementsByClassName("bilibili-player-video-danmaku")[0] || document.getElementsByClassName("bpx-player-row-dm-wrap")[0] || document.getElementsByClassName("web-player-danmaku")[0] || document.getElementsByClassName("danmaku-screen")[0]; if(nowDanmuHolder != danmuHolder || width != video.videoWidth || height != video.videoHeight) { danmuHolder = nowDanmuHolder; danmuObserver.disconnect(); width = video.videoWidth; height = video.videoHeight; canvasWidth = canvas.width = (Math.min(height, width) < 700 ? width : Math.floor(width / 2)); canvasHeight = canvas.height = (Math.min(height, width) < 700 ? height : Math.floor(height / 2)); textCanvas.height = danmuFontsize = Math.floor(Math.min(canvasWidth, canvasHeight) / 14.5); textCanvas.width = danmuFontsize * 35; textCanvasContext.textBaseline = "top"; textCanvasContext.shadowBlur = 3; textCanvasContext.shadowColor = "rgb(0, 0, 0)"; textCanvasContext.font = danmuFontsize + 'px SimHei,"Microsoft JhengHei",Arial,Helvetica,sans-serif'; nVideo.srcObject = canvas.captureStream(60); setTimeout(() => nVideo.play(), 1500); danmuObserver.observe(danmuHolder, { childList: true, subtree: true }); console.log("[哔哩哔哩画中画弹幕]", "视频切换") console.log("[哔哩哔哩画中画弹幕]", "视频分辨率", width, height); console.log("[哔哩哔哩画中画弹幕]", "渲染分辨率", canvasWidth, canvasHeight); } }, 900); var lastTime = Date.now(); function draw() { var nowTime = Date.now(); var timeInterval = nowTime - lastTime; lastTime = nowTime; if(video) { context.globalAlpha = 1; context.drawImage(video, 0, 0, width, height, 0, 0, canvasWidth, canvasHeight); if(video.readyState >= 1) { context.globalAlpha = 0.7; danmuList.forEach(o => { context.drawImage(o.i, Math.floor(o.x), Math.floor(o.y)); if(!video.paused) { o.x -= timeInterval * danmuFontsize * 0.0035; } }); danmuList = danmuList.filter(o => (o.x >= -o.w)); } else { danmuList = []; } } timeoutId = setTimeout(draw, Math.floor(1000 / 60)); } window.pipdm = { get help() { console.log("[哔哩哔哩画中画弹幕]",([ "画中画弹幕插件指令帮助", "pipdm.maxLine 修改画中画弹幕最大行数", "pipdm.help 显示此帮助文本" ]).join("\n")); }, get maxLine() { var newValue = 0 | (prompt("设置画中画弹幕最大行数", danmuMaxLine)); if(newValue != undefined && newValue >= 0) danmuMaxLine = newValue; } }; console.log("[哔哩哔哩画中画弹幕]", "已加载"); console.log("[哔哩哔哩画中画弹幕]", "输入 pipdm.help 以显示指令帮助"); }, 500);