Greasy Fork is available in English.
哔哩哔哩画中画支持显示弹幕
- // ==UserScript==// @name 哔哩哔哩画中画弹幕// @namespace qwq0// @version 0.27// @description 哔哩哔哩画中画支持显示弹幕// @author QwQ~// @match https://www.bilibili.com/video/*// @match https://www.bilibili.com/medialist/play/*// @match https://www.bilibili.com/list/*// @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 GM_setValue// @grant GM_getValue// @grant unsafeWindow// ==/UserScript==setTimeout(function (){"use strict";function setValue(name, value){if (window["GM_setValue"])GM_setValue(name, value);}function getValue(name, defaultValue){return (window["GM_getValue"] ? GM_getValue(name, defaultValue) : defaultValue);}let videoHolder = null;let video = null;let width = 0;let height = 0;let canvas = document.createElement("canvas");let context = canvas.getContext("2d");let canvasWidth = canvas.width = 0;let canvasHeight = canvas.height = 0;let danmuFontsize = 0;let textCanvasArray = Array(3).fill(0).map(() => document.createElement("canvas"));let textCanvasContextArray = textCanvasArray.map(o => o.getContext("2d"));let nVideo = document.createElement("video");let timeoutId = 0;let forceRequestAnimationFrame = true;let isFirefox = navigator.userAgent.indexOf("Firefox") > -1;if (isFirefox){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));}let lastPlayOrPauseTime = 0;nVideo.addEventListener("play", () =>{if (video && performance.now() - lastPlayOrPauseTime > 15){lastPlayOrPauseTime = performance.now();video.play();}});nVideo.addEventListener("pause", () =>{if (video && performance.now() - lastPlayOrPauseTime > 15){lastPlayOrPauseTime = performance.now();video.pause();}});let customDanmuMaxLine = getValue("danmuMaxLine", 12);let danmuList = [];let danmuLineMaxX = [];let danmuLineLock = [];let danmuMaxLine = Number(customDanmuMaxLine ? customDanmuMaxLine : 12);let danmuMaxCount = 50;let danmuLogOutput = false;let danmuRendering = false;if (!Number.isInteger(danmuMaxLine))danmuMaxLine = 12;async function addDanmu(text, color){if (text != "" &&timeoutId &&!danmuRendering &&textCanvasContextArray.length > 0 &&danmuList.length <= danmuMaxCount &&(danmuList.length <= 20 || Math.random() < 20 / danmuList.length)){let lineNum = 0;for (let i = 0; i < danmuMaxLine; i++){lineNum = i;if (!(danmuLineMaxX[lineNum] > canvasWidth || danmuLineLock[lineNum]))break;else if (i + 1 == danmuMaxLine)return;}if (!color)color = "rgb(255, 255, 255)";let textCanvasContext = textCanvasContextArray.pop();let textCanvas = textCanvasContext.canvas;let textWidth = textCanvasContext.measureText(text).width;textCanvasContext.clearRect(0, 0, textWidth, danmuFontsize);textCanvasContext.fillStyle = color;textCanvasContext.fillText(text, 0, 0);if (textWidth > 0){danmuLineLock[lineNum] = true;danmuList.push({ text: text, color: color, x: canvasWidth, y: lineNum * danmuFontsize, l: lineNum, w: textWidth, i: await createImageBitmap(textCanvas, 0, 0, textWidth, danmuFontsize) });danmuLineLock[lineNum] = false;danmuLineMaxX[lineNum] = canvasWidth + textWidth;}textCanvasContextArray.push(textCanvasContext);}}let 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 = String(ele.innerText);let color = ele.style.color;if (!color)color = ele.style.getPropertyValue("--color");if (ele.style.opacity != "0")addDanmu(text.split("\n")[0], color);if (danmuLogOutput)console.log("danmu(it)", color, text, ele);}else if (ele.textContent){let text = String(ele.textContent);let color = o.target.style.color;if (!color)color = o.target.style.getPropertyValue("--color");addDanmu(text.split("\n")[0], color);if (danmuLogOutput)console.log("danmu(ct)", color, text, ele);}});}});});let danmuHolder = null;let subtitlePanel = null;setInterval(() =>{let 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;let nowVideo = nowVideoHolder.getElementsByTagName("video")[0];if (nowVideo && video != nowVideo){videoHolder = nowVideoHolder;video = nowVideo;video.addEventListener("play", () =>{console.log("[哔哩哔哩画中画弹幕]", "视频播放");nVideo.play();});video.addEventListener("pause", () =>{console.log("[哔哩哔哩画中画弹幕]", "视频暂停");nVideo.pause();});video.addEventListener("enterpictureinpicture", () =>{if (!timeoutId){timeoutId = setTimeout(draw, Math.floor(1000 / 60));setTimeout(() =>{nVideo.requestPictureInPicture();nVideo.play();}, 250);}else{nVideo.requestPictureInPicture();nVideo.play();}});let 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("[哔哩哔哩画中画弹幕]", "绑定媒体功能键时发生错误");}}}let 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));danmuFontsize = Math.floor(Math.min(canvasWidth, canvasHeight) / 14.5);textCanvasArray.forEach(o =>{o.height = danmuFontsize;o.width = danmuFontsize * 35;});textCanvasContextArray.forEach(o =>{o.textBaseline = "top";o.shadowBlur = 3;o.shadowColor = "rgb(0, 0, 0)";o.font = `${danmuFontsize}px SimHei,"Microsoft JhengHei",Arial,Helvetica,sans-serif`;});nVideo.srcObject = canvas.captureStream(60);setTimeout(() =>{if (video && !video.paused)nVideo.play();}, 1500);danmuObserver.observe(danmuHolder, { childList: true, subtree: true });console.log("[哔哩哔哩画中画弹幕]", "视频切换");console.log("[哔哩哔哩画中画弹幕]", "视频分辨率", width, height);console.log("[哔哩哔哩画中画弹幕]", "渲染分辨率", canvasWidth, canvasHeight);}let nowSubtitlePanel = document.getElementsByClassName("bpx-player-subtitle-panel-wrap")[0];subtitlePanel = nowSubtitlePanel;}, 900);let lastTime = performance.now();function draw(){let nowTime = performance.now();let 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;danmuLineMaxX.length = 0;danmuList = danmuList.filter(o =>{if (!video.paused)o.x -= timeInterval * danmuFontsize * 0.0035;let rightX = o.x + o.w;if (rightX <= 0)return false;context.drawImage(o.i, Math.round(o.x), Math.round(o.y));if (!(rightX <= danmuLineMaxX[o.l]))danmuLineMaxX[o.l] = rightX;return true;});let subtitleText = (subtitlePanel ? subtitlePanel.innerText : "");if (subtitleText){context.globalAlpha = 0.5;context.fillStyle = "rgb(0, 0, 0)";let subtitleWidth = context.measureText(subtitleText).width;context.fillRect((canvasWidth - subtitleWidth) / 2, canvasHeight - danmuFontsize * 1.5, subtitleWidth, danmuFontsize);context.globalAlpha = 1;context.textBaseline = "bottom";context.textAlign = "center";context.font = `${danmuFontsize}px SimHei,"Microsoft JhengHei",Arial,Helvetica,sans-serif`;context.fillStyle = "rgb(255, 255, 255)";context.fillText(subtitleText, canvasWidth / 2, canvasHeight - danmuFontsize * 0.5);}}else{danmuList.length = 0;}}if (forceRequestAnimationFrame || isFirefox)timeoutId = requestAnimationFrame(draw);elsetimeoutId = setTimeout(draw, Math.floor(1000 / 60));}let pipdmCommandObj = {help: () =>{console.log("[哔哩哔哩画中画弹幕]", (["画中画弹幕插件指令帮助","pipdm.maxLine 修改画中画弹幕最大行数","pipdm.danmuLog 开启弹幕日志输出","pipdm.help 显示此帮助文本"]).join("\n"));},maxLine: () =>{let newValue = 0 | (prompt("设置画中画弹幕最大行数", danmuMaxLine));if (newValue != undefined && newValue > 0 && Number.isInteger(newValue)){if (newValue > 16)danmuMaxLine = 16;elsedanmuMaxLine = newValue;console.log("[哔哩哔哩画中画弹幕]", `已将画中画弹幕最大行数设置为 ${danmuMaxLine} 行`);setValue("danmuMaxLine", danmuMaxLine);}else{console.log("[哔哩哔哩画中画弹幕]", `设置的数值无效`);}},danmuLog: () =>{danmuLogOutput = !danmuLogOutput;console.log("[哔哩哔哩画中画弹幕]", `已${danmuLogOutput ? "开启" : "关闭"}弹幕日志输出`);}};(window["unsafeWindow"] ? unsafeWindow : window).pipdm = new Proxy({maxLine: "修改画中画弹幕最大行数",danmuLog: "开启弹幕日志输出",help: "显示指令帮助"}, {get: (target, key) =>{if (pipdmCommandObj[key])pipdmCommandObj[key]();elseconsole.log("[哔哩哔哩画中画弹幕]", "不存在此指令\n输入 pipdm.help 以显示指令帮助");return () => { };}});console.log("[哔哩哔哩画中画弹幕]", "已加载");console.log("[哔哩哔哩画中画弹幕]", "输入 pipdm.help 以显示指令帮助");}, 500);