SakuraDanmakuClasses

Library: Classes for SakuraDanmaku

此脚本不应直接安装,它是一个供其他脚本使用的外部库。如果您需要使用该库,请在脚本元属性加入:// @require https://update.gf.qytechs.cn/scripts/454443/1122392/SakuraDanmakuClasses.js

  1. // @name SakuraDanmakuClasses
  2. // @namespace https://muted.top/
  3. // @version 0.9.1
  4. // @description Classes for SakuraDanmaku
  5. // @author MUTED64
  6.  
  7. class BilibiliDanmaku {
  8. static #EP_API_BASE = "https://api.bilibili.com/pgc/view/web/season";
  9. static #DANMAKU_API_BASE = "https://api.bilibili.com/x/v1/dm/list.so";
  10. static #KEYWORD_API_BASE =
  11. "https://api.bilibili.com/x/web-interface/search/type?search_type=media_bangumi";
  12.  
  13. constructor(keyword, episode) {
  14. this.keyword = keyword;
  15. this.episode = episode;
  16. }
  17.  
  18. // GM_xmlhttpRequest的Promise封装
  19. #makeGetRequest(url) {
  20. return new Promise((resolve, reject) => {
  21. GM_xmlhttpRequest({
  22. method: "GET",
  23. url: url,
  24. onload: (response) => {
  25. resolve(response.responseText);
  26. },
  27. onerror: (error) => {
  28. reject(error);
  29. },
  30. });
  31. });
  32. }
  33.  
  34. // Bilibili弹幕xml串转换为可加载的对象
  35. #parseBilibiliDanmaku(string) {
  36. const $xml = new DOMParser().parseFromString(string, "text/xml");
  37. return [...$xml.getElementsByTagName("d")]
  38. .map(($d) => {
  39. const p = $d.getAttribute("p");
  40. if (p === null || $d.childNodes[0] === undefined) return null;
  41. const values = p.split(",");
  42. const mode = { 6: "ltr", 1: "rtl", 5: "top", 4: "bottom" }[values[1]];
  43. if (!mode) return null;
  44. const fontSize = Number(values[2]) || 25;
  45. const color = `000000${Number(values[3]).toString(16)}`.slice(-6);
  46. return {
  47. text: $d.childNodes[0].nodeValue,
  48. mode,
  49. time: values[0] * 1,
  50. baseTime: values[0] * 1,
  51. style: {
  52. fontSize: `${fontSize}px`,
  53. color: `#${color}`,
  54. textShadow: "0px 1px 3px #000,0px 0px 3px #000",
  55. font: `${fontSize}px sans-serif`,
  56. fillStyle: `#${color}`,
  57. strokeStyle: color === "000000" ? "#fff" : "#000",
  58. lineWidth: 2.0,
  59. },
  60. };
  61. })
  62. .filter((x) => x);
  63. }
  64.  
  65. // 获取Bilibili对应视频的弹幕
  66. async getInfoAndDanmaku(xml = undefined) {
  67. if (!xml) {
  68. const fetchedFromKeyword = JSON.parse(
  69. await this.#makeGetRequest(
  70. `${this.constructor.#KEYWORD_API_BASE}&keyword=${this.keyword}`
  71. )
  72. ).data.result;
  73.  
  74. this.mdid = fetchedFromKeyword[0].media_id;
  75. this.ssid = fetchedFromKeyword[0].season_id;
  76. this.epid = fetchedFromKeyword[0].eps[0].id;
  77.  
  78. // 获取cid
  79. let { code, message, result } = JSON.parse(
  80. await this.#makeGetRequest(
  81. `${this.constructor.#EP_API_BASE}?ep_id=${this.epid}`
  82. )
  83. );
  84. if (code) {
  85. throw new Error(message);
  86. }
  87. this.cid = result.episodes[this.episode - 1].cid;
  88.  
  89. // 获取弹幕
  90. this.comments = this.#parseBilibiliDanmaku(
  91. await this.#makeGetRequest(
  92. `${this.constructor.#DANMAKU_API_BASE}?oid=${this.cid}`
  93. )
  94. );
  95. this.basic_info = {
  96. mdid: this.mdid,
  97. ssid: this.ssid,
  98. epid: this.epid,
  99. cid: this.cid,
  100. comments: this.comments,
  101. };
  102. return this.basic_info;
  103. } else {
  104. this.basic_info = { comments: this.#parseBilibiliDanmaku(xml) };
  105. return this.basic_info;
  106. }
  107. }
  108. }
  109.  
  110. class DanmakuLoader {
  111. danmaku;
  112.  
  113. constructor(keyword, episode, container, video) {
  114. this.keyword = keyword;
  115. this.episode = episode;
  116. this.container = container;
  117. this.video = video;
  118. }
  119.  
  120. async #loadDanmaku(xml) {
  121. const bilibiliDanmaku = new BilibiliDanmaku(this.keyword, this.episode);
  122. this.basic_info = await bilibiliDanmaku.getInfoAndDanmaku(xml);
  123. this.danmaku = new Danmaku({
  124. container: this.container,
  125. media: this.video,
  126. comments: this.basic_info.comments,
  127. speed: 144,
  128. });
  129. }
  130.  
  131. async showDanmaku(xml) {
  132. await this.#loadDanmaku(xml);
  133. this.video.style.position = "absolute";
  134. this.danmaku.show();
  135. let resizeObserver = new ResizeObserver(() => {
  136. this.danmaku.resize();
  137. });
  138. resizeObserver.observe(this.container);
  139. return this.danmaku;
  140. }
  141. }
  142.  
  143. class DanmakuSettings {
  144. danmakuWrapper;
  145. danmakuButton;
  146. danmakuSettingBox;
  147. buttonHtml =
  148. "<button class=\"dplayer-icon dplayer-comment-icon\" data-balloon=\"弹幕设置\" data-balloon-pos=\"up\"><span class=\"dplayer-icon-content\"><svg xmlns=\"http://www.w3.org/2000/svg\" version=\"1.1\" viewBox=\"0 0 32 32\"><path d=\"M27.128 0.38h-22.553c-2.336 0-4.229 1.825-4.229 4.076v16.273c0 2.251 1.893 4.076 4.229 4.076h4.229v-2.685h8.403l-8.784 8.072 1.566 1.44 7.429-6.827h9.71c2.335 0 4.229-1.825 4.229-4.076v-16.273c0-2.252-1.894-4.076-4.229-4.076zM28.538 19.403c0 1.5-1.262 2.717-2.819 2.717h-8.36l-0.076-0.070-0.076 0.070h-11.223c-1.557 0-2.819-1.217-2.819-2.717v-13.589c0-1.501 1.262-2.718 2.819-2.718h19.734c1.557 0 2.819-0.141 2.819 1.359v14.947zM9.206 10.557c-1.222 0-2.215 0.911-2.215 2.036s0.992 2.035 2.215 2.035c1.224 0 2.216-0.911 2.216-2.035s-0.992-2.036-2.216-2.036zM22.496 10.557c-1.224 0-2.215 0.911-2.215 2.036s0.991 2.035 2.215 2.035c1.224 0 2.215-0.911 2.215-2.035s-0.991-2.036-2.215-2.036zM15.852 10.557c-1.224 0-2.215 0.911-2.215 2.036s0.991 2.035 2.215 2.035c1.222 0 2.215-0.911 2.215-2.035s-0.992-2.036-2.215-2.036z\"></path></svg></span></button>";
  149.  
  150. constructor(danmakuLoader, danmakuDOM, iconsBar, iframe) {
  151. this.danmaku = danmakuLoader.danmaku;
  152. this.danmakuLoader = danmakuLoader;
  153. this.danmakuDOM = danmakuDOM;
  154. this.iconsBar = iconsBar;
  155. this.iframe = iframe;
  156. this.danmakuSettings = GM_getValue("danmakuSettings", {
  157. show: true,
  158. speed: 144,
  159. opacity: 1,
  160. fontSize: 25,
  161. limit: 1,
  162. });
  163. this.#createButton();
  164. this.#createSettingBox();
  165. this.#addSettingItems();
  166. }
  167.  
  168. #createButton() {
  169. if(this.iframe.querySelector("#danmaku-wrapper")){
  170. this.iframe.querySelector("#danmaku-wrapper").remove();
  171. }
  172. // 创建按钮
  173. this.danmakuWrapper = this.iframe.createElement("div");
  174. this.danmakuWrapper.setAttribute("id", "danmaku-wrapper");
  175. this.danmakuWrapper.setAttribute("class", "dplayer-setting");
  176. this.danmakuWrapper.setAttribute(
  177. "style",
  178. "display:inline-block;height:100%;"
  179. );
  180. this.danmakuWrapper.innerHTML = this.buttonHtml;
  181. // 添加到播放器
  182. this.iconsBar.prepend(this.danmakuWrapper);
  183. this.danmakuButton = this.danmakuWrapper.firstChild;
  184.  
  185. // 设定打开和关闭面板的操作
  186. this.danmakuButton.addEventListener("click", () => {
  187. if (
  188. this.danmakuSettingBox.style.transform === "scale(0)" ||
  189. !this.danmakuSettingBox.style.transform
  190. ) {
  191. this.danmakuSettingBox.style.transform = "scale(1)";
  192. } else {
  193. this.danmakuSettingBox.style.transform = "scale(0)";
  194. }
  195. });
  196. this.iframe.addEventListener("click", (e) => {
  197. if (
  198. !e.composedPath().includes(this.danmakuButton) &&
  199. !e.composedPath().includes(this.danmakuSettingBox)
  200. ) {
  201. this.danmakuSettingBox.style.transform = "scale(0)";
  202. }
  203. });
  204.  
  205. // 调整一下底栏样式,原来的看不太清
  206. this.iframe.querySelector(".dplayer-controller").style.backgroundColor =
  207. "#0002";
  208. }
  209.  
  210. #createSettingBox() {
  211. this.danmakuSettingBox = this.iframe.createElement("div");
  212. this.danmakuSettingBox.setAttribute("class", "dplayer-setting-box");
  213. this.danmakuSettingBox.setAttribute("style", "transform: scale(0);");
  214. this.danmakuSettingBox.setAttribute("style", "width: 200px;");
  215. this.danmakuWrapper.appendChild(this.danmakuSettingBox);
  216. }
  217.  
  218. #addSettingItems() {
  219. this.#addShowOrHide();
  220. this.#addDanmakuSpeed();
  221. this.#addDanmakuOpacity();
  222. this.#addDanmakuFontSize();
  223. this.#addDanmakuLimit();
  224. this.#addOffsetSetting();
  225. }
  226.  
  227. #addShowOrHide() {
  228. this.showOrHideDanmaku = this.iframe.createElement("div");
  229. this.showOrHideDanmaku.setAttribute(
  230. "class",
  231. "dplayer-setting-item show-danmaku"
  232. );
  233. this.showOrHideDanmaku.style.display = "block";
  234. this.showOrHideDanmaku.innerHTML =
  235. "<span class=\"dplayer-label\">显示弹幕</span>";
  236. this.showOrHideToggle = this.iframe.createElement("input");
  237. this.showOrHideToggle.setAttribute("type", "checkbox");
  238. this.danmakuSettings.show
  239. ? this.showOrHideToggle.setAttribute("checked", "checked")
  240. : null;
  241. this.showOrHideToggle.style.float = "right";
  242. this.danmakuDOM.setAttribute(
  243. "style",
  244. this.danmakuSettings.show ? "display:block;" : "display:none;"
  245. );
  246. this.showOrHideToggle.addEventListener("click", () => {
  247. if (this.showOrHideToggle.checked) {
  248. this.danmakuDOM.setAttribute("style", "display:block;");
  249. this.danmakuSettings.show = true;
  250. GM_setValue("danmakuSettings", this.danmakuSettings);
  251. } else {
  252. this.danmakuDOM.setAttribute("style", "display:none;");
  253. this.danmakuSettings.show = false;
  254. GM_setValue("danmakuSettings", this.danmakuSettings);
  255. }
  256. });
  257. this.showOrHideDanmaku.appendChild(this.showOrHideToggle);
  258. this.danmakuSettingBox.appendChild(this.showOrHideDanmaku);
  259. }
  260.  
  261. #addDanmakuSpeed() {
  262. this.danmakuSpeed = this.iframe.createElement("div");
  263. this.danmakuSpeed.setAttribute(
  264. "class",
  265. "dplayer-setting-item speed-danmaku"
  266. );
  267. this.danmakuSpeed.style.display = "block";
  268. this.danmakuSpeed.innerHTML = "<span class=\"dplayer-label\">弹幕速度</span>";
  269. this.danmakuSpeedRange = this.iframe.createElement("input");
  270. this.danmakuSpeedRange.setAttribute("type", "range");
  271. this.danmakuSpeedRange.style.display = "inline-block";
  272. this.danmakuSpeedRange.style.float = "right";
  273. this.danmakuSpeedRange.style.width = "50%";
  274. this.danmakuSpeedRange.setAttribute("min", 72);
  275. this.danmakuSpeedRange.setAttribute("max", 288);
  276. this.danmakuSpeedRange.setAttribute("value", this.danmakuSettings.speed);
  277. this.danmaku.speed = this.danmakuSettings.speed;
  278. this.danmakuSpeedRange.addEventListener("input", () => {
  279. this.danmakuSettings.speed = Number(this.danmakuSpeedRange.value);
  280. GM_setValue("danmakuSettings", this.danmakuSettings);
  281. this.danmaku.speed = this.danmakuSettings.speed;
  282. });
  283. this.danmakuSpeed.appendChild(this.danmakuSpeedRange);
  284. this.danmakuSettingBox.appendChild(this.danmakuSpeed);
  285. }
  286.  
  287. #addDanmakuOpacity() {
  288. this.danmakuOpacity = this.iframe.createElement("div");
  289. this.danmakuOpacity.setAttribute(
  290. "class",
  291. "dplayer-setting-item transparency-danmaku"
  292. );
  293. this.danmakuOpacity.style.display = "block";
  294. this.danmakuOpacity.innerHTML =
  295. "<span class=\"dplayer-label\">弹幕透明度</span>";
  296. this.danmakuOpacityRange = this.iframe.createElement("input");
  297. this.danmakuOpacityRange.setAttribute("type", "range");
  298. this.danmakuOpacityRange.style.display = "inline-block";
  299. this.danmakuOpacityRange.style.float = "right";
  300. this.danmakuOpacityRange.style.width = "50%";
  301. this.danmakuOpacityRange.setAttribute("min", 0);
  302. this.danmakuOpacityRange.setAttribute("max", 1);
  303. this.danmakuOpacityRange.setAttribute("step", 0.1);
  304. this.danmakuOpacityRange.setAttribute(
  305. "value",
  306. this.danmakuSettings.opacity
  307. );
  308. this.danmakuDOM.style.opacity = this.danmakuSettings.opacity;
  309. this.danmakuOpacityRange.addEventListener("input", () => {
  310. this.danmakuSettings.opacity = Number(this.danmakuOpacityRange.value);
  311. GM_setValue("danmakuSettings", this.danmakuSettings);
  312. this.danmakuDOM.style.opacity = this.danmakuSettings.opacity;
  313. });
  314. this.danmakuOpacity.appendChild(this.danmakuOpacityRange);
  315. this.danmakuSettingBox.appendChild(this.danmakuOpacity);
  316. }
  317.  
  318. #addDanmakuFontSize() {
  319. this.danmakuFontSize = this.iframe.createElement("div");
  320. this.danmakuFontSize.setAttribute(
  321. "class",
  322. "dplayer-setting-item font-size-danmaku"
  323. );
  324. this.danmakuFontSize.style.display = "block";
  325. this.danmakuFontSize.innerHTML =
  326. "<span class=\"dplayer-label\">弹幕字体大小</span>";
  327. this.danmakuFontSizeRange = this.iframe.createElement("input");
  328. this.danmakuFontSizeRange.setAttribute("type", "range");
  329. this.danmakuFontSizeRange.style.display = "inline-block";
  330. this.danmakuFontSizeRange.style.float = "right";
  331. this.danmakuFontSizeRange.style.width = "50%";
  332. this.danmakuFontSizeRange.setAttribute("min", 16);
  333. this.danmakuFontSizeRange.setAttribute("max", 32);
  334. this.danmakuFontSizeRange.setAttribute("step", 1);
  335. this.danmakuFontSizeRange.setAttribute(
  336. "value",
  337. this.danmakuSettings.fontSize
  338. );
  339. setDanmakuFontSize(this.danmakuSettings.fontSize, this.danmaku);
  340. this.danmakuFontSizeRange.addEventListener("input", () => {
  341. this.danmakuSettings.fontSize = Number(this.danmakuFontSizeRange.value);
  342. GM_setValue("danmakuSettings", this.danmakuSettings);
  343. setDanmakuFontSize(this.danmakuSettings.fontSize, this.danmaku);
  344. });
  345. this.danmakuFontSize.appendChild(this.danmakuFontSizeRange);
  346. this.danmakuSettingBox.appendChild(this.danmakuFontSize);
  347.  
  348. function setDanmakuFontSize(fontSize, danmaku) {
  349. for (const i of danmaku.comments) {
  350. i.style.font = `${fontSize}px sans-serif`;
  351. }
  352. }
  353. }
  354.  
  355. #addDanmakuLimit() {
  356. this.danmakuLimit = this.iframe.createElement("div");
  357. this.danmakuLimit.setAttribute(
  358. "class",
  359. "dplayer-setting-item limit-danmaku"
  360. );
  361. this.danmakuLimit.style.display = "block";
  362. this.danmakuLimit.innerHTML = "<span class=\"dplayer-label\">弹幕密度</span>";
  363. this.danmakuLimitRange = this.iframe.createElement("input");
  364. this.danmakuLimitRange.setAttribute("type", "range");
  365. this.danmakuLimitRange.style.display = "inline-block";
  366. this.danmakuLimitRange.style.float = "right";
  367. this.danmakuLimitRange.style.width = "50%";
  368. this.danmakuLimitRange.setAttribute("min", 0);
  369. this.danmakuLimitRange.setAttribute("max", 1);
  370. this.danmakuLimitRange.setAttribute("step", 0.01);
  371. this.danmakuLimitRange.setAttribute("value", this.danmakuSettings.limit);
  372. limitDanmaku(this.danmakuSettings.limit, this.danmaku);
  373. this.danmakuLimitRange.addEventListener("input", () => {
  374. this.danmakuSettings.limit = Number(this.danmakuLimitRange.value);
  375. GM_setValue("danmakuSettings", this.danmakuSettings);
  376. limitDanmaku(this.danmakuSettings.limit, this.danmaku);
  377. });
  378. this.danmakuLimit.appendChild(this.danmakuLimitRange);
  379. this.danmakuSettingBox.appendChild(this.danmakuLimit);
  380.  
  381. function limitDanmaku(percent, danmaku) {
  382. for (const i of danmaku.comments) {
  383. i.style.display = "block";
  384. if (Math.random() > percent) {
  385. i.style.display = "none";
  386. }
  387. }
  388. }
  389. }
  390.  
  391. #addOffsetSetting() {
  392. this.offsetSetting = this.iframe.createElement("div");
  393. this.offsetSetting.setAttribute("class", "dplayer-setting-item offset");
  394. this.offsetSetting.style.display = "block";
  395. this.offsetSetting.innerHTML =
  396. "<span class=\"dplayer-label\">弹幕偏移(s)</span>";
  397. this.offsetNumber = this.iframe.createElement("input");
  398. this.offsetNumber.setAttribute("type", "number");
  399. this.offsetNumber.style.display = "inline-block";
  400. this.offsetNumber.style.float = "right";
  401. this.offsetNumber.style.width = "47%";
  402. this.offsetNumber.setAttribute("min", -10);
  403. this.offsetNumber.setAttribute("max", 10);
  404. this.offsetNumber.setAttribute("step", 0.1);
  405. this.offsetNumber.setAttribute("value", 0);
  406. this.offsetNumber.addEventListener("input", () => {
  407. for (const comment of this.danmaku.comments) {
  408. comment.time = comment.baseTime - Number(this.offsetNumber.value);
  409. }
  410. this.danmakuLoader.video.currentTime = Number(
  411. this.danmakuLoader.video.currentTime
  412. );
  413. });
  414. this.offsetSetting.appendChild(this.offsetNumber);
  415. this.danmakuSettingBox.appendChild(this.offsetSetting);
  416. }
  417. }

QingJ © 2025

镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址