B站播放器动态视频预览

为B站动态页面的视频添加预览功能

  1. // ==UserScript==
  2. // @name B站播放器动态视频预览
  3. // @namespace http://tampermonkey.net/
  4. // @version 4.0
  5. // @description 为B站动态页面的视频添加预览功能
  6. // @author Ts8zs
  7. // @license GPL
  8. // @match https://player.bilibili.com/player.html
  9. // @match https://t.bilibili.com/
  10. // @icon https://www.bilibili.com/favicon.ico
  11. // @grant GM_addStyle
  12. // ==/UserScript==
  13.  
  14. (function () {
  15. "use strict";
  16.  
  17. // 监听播放结束事件
  18. const video = document.querySelector("video");
  19. if (video) {
  20. video.addEventListener("ended", () => {
  21. window.parent.postMessage("bilibili:player:ended", "*");
  22. });
  23. }
  24.  
  25. // 样式注入
  26. GM_addStyle(`
  27. .bili-dyn-home--member {
  28. justify-content: left !important;
  29. padding: 10px;
  30. }
  31. .preview-btn {
  32. position: absolute;
  33. left: 10px;
  34. bottom: 10px;
  35. z-index: 1000000;
  36. padding: 5px 10px;
  37. background: rgba(128, 128, 128, 0.2);
  38. color: white;
  39. border-radius: 4px;
  40. cursor: pointer;
  41. transition: opacity 0.3s;
  42. }
  43. .preview-btn:hover {
  44. opacity: 0.8;
  45. }
  46. #preview-container {
  47. position: fixed;
  48. right: 20px;
  49. bottom: 20px;
  50. width: 48vw;
  51. height: 27vw;
  52. background: black;
  53. z-index: 9999;
  54. border-radius: 8px;
  55. box-shadow: 0 0 10px #000;
  56. overflow: hidden;
  57. transition: all 0.3s;
  58. }
  59. #preview-container.maximized {
  60. width: 100vw;
  61. height: 100vh;
  62. right: 0;
  63. bottom: 0;
  64. z-index: 10000000;
  65. transform: translate(0, 0); /* 确保全屏时容器居中 */
  66. }
  67. #preview-player {
  68. width: 100%;
  69. height: 100%;
  70. border-width: 0px;
  71. }
  72. .preview-controls {
  73. position: absolute;
  74. top: 10px;
  75. right: 10px;
  76. display: flex;
  77. gap: 10px;
  78. }
  79. .control-btn {
  80. padding: 5px 10px;
  81. background: rgba(0, 0, 0, 0.2);
  82. opacity:0.2;
  83. color: white;
  84. border-radius: 4px;
  85. cursor: pointer;
  86. }
  87. .control-btn:hover {
  88. opacity:1;
  89. background: rgba(0, 0, 0, 0.4);
  90. }
  91. `);
  92.  
  93. // 视频队列和状态管理
  94. let currentIndex = 0;
  95. let videoList = [];
  96. let isMaximized = false;
  97.  
  98. // 初始化预览容器
  99. const initPreviewContainer = () => {
  100. const container = document.createElement("div");
  101. container.id = "preview-container";
  102. container.style.display = "none"; // 初始时隐藏预览容器
  103. container.innerHTML = `
  104. <div class="preview-controls">
  105. <div class="control-btn" id="next-video">⏭</div>
  106. <div class="control-btn" id="maximize">⛶</div>
  107. <div class="control-btn" id="close-preview">✕</div> <!-- 添加关闭按钮 -->
  108. </div>
  109. <iframe id="preview-player" allowfullscreen></iframe>
  110. `;
  111. document.body.appendChild(container);
  112.  
  113. // 控制按钮事件
  114. container
  115. .querySelector("#maximize")
  116. .addEventListener("click", toggleMaximize);
  117. container.querySelector("#next-video").addEventListener("click", playNext);
  118. container
  119. .querySelector("#close-preview")
  120. .addEventListener("click", closePreview); // 添加关闭按钮事件
  121. };
  122.  
  123. // 最大化切换
  124. const toggleMaximize = () => {
  125. const container = document.querySelector("#preview-container");
  126. isMaximized = !isMaximized;
  127. container.classList.toggle("maximized", isMaximized);
  128. };
  129.  
  130. // 播放控制
  131. const playVideo = (index) => {
  132. const container = document.querySelector("#preview-container");
  133. container.style.display = "block"; // 播放时显示预览容器
  134. currentIndex = index;
  135. const video = videoList[currentIndex];
  136. const iframe = document.querySelector("#preview-player");
  137.  
  138. // 隐藏 .left 和 .right 元素
  139. document
  140. .querySelectorAll(".left, .right")
  141. .forEach((el) => (el.style.display = "none"));
  142.  
  143. // 使用新的播放器参数支持事件监听
  144. iframe.src = `https://player.bilibili.com/player.html?bvid=${video.bvid}&autoplay=1&page=1&enable_ssl=1&crossDomain=1`;
  145.  
  146. // 监听播放结束事件
  147. window.addEventListener("message", (e) => {
  148. if (e.origin !== "https://player.bilibili.com") return;
  149. if (e.data === "bilibili:player:ended") {
  150. playNext();
  151. }
  152. });
  153. };
  154.  
  155. const playNext = () => {
  156. if (currentIndex < videoList.length - 1) {
  157. // 滚动到对应的视频链接位置
  158. const nextVideoElement = videoList[currentIndex + 1].element;
  159. nextVideoElement.scrollIntoView({ behavior: "smooth", block: "center" });
  160. playVideo(currentIndex + 1);
  161. }
  162. };
  163.  
  164. // 关闭预览容器并恢复 .left 和 .right 元素
  165. const closePreview = () => {
  166. const container = document.querySelector("#preview-container");
  167. container.style.display = "none";
  168. document
  169. .querySelectorAll(".left, .right")
  170. .forEach((el) => (el.style.display = ""));
  171. const iframe = document.querySelector("#preview-player");
  172. if (iframe) {
  173. iframe.remove(); // 删除 iframe 元素
  174. }
  175. container.innerHTML = `
  176. <div class="preview-controls">
  177. <div class="control-btn" id="maximize">⛶</div>
  178. <div class="control-btn" id="next-video">⏭</div>
  179. <div class="control-btn" id="close-preview">✕</div> <!-- 添加关闭按钮 -->
  180. </div>
  181. <iframe id="preview-player" allowfullscreen></iframe>
  182. `;
  183. // 重新绑定事件
  184. container
  185. .querySelector("#maximize")
  186. .addEventListener("click", toggleMaximize);
  187. container.querySelector("#next-video").addEventListener("click", playNext);
  188. container
  189. .querySelector("#close-preview")
  190. .addEventListener("click", closePreview); // 添加关闭按钮事件
  191. };
  192.  
  193. // 动态检测处理器
  194. const processCards = () => {
  195. document.querySelectorAll(".bili-dyn-list__item").forEach((card) => {
  196. if (card.dataset.processed) return;
  197.  
  198. const videoLink = card.querySelector('a[href*="/video/BV"]');
  199. if (videoLink) {
  200. const bvid = videoLink.href.match(/video\/(BV\w+)/)[1];
  201. const previewBtn = document.createElement("div");
  202. previewBtn.className = "preview-btn";
  203. previewBtn.textContent = "▶";
  204.  
  205. previewBtn.addEventListener("click", (e) => {
  206. e.stopPropagation();
  207. videoList = Array.from(
  208. document.querySelectorAll(".bili-dyn-list__item")
  209. )
  210. .filter((c) => c.querySelector('a[href*="/video/BV"]'))
  211. .map((c) => ({
  212. element: c,
  213. bvid: c
  214. .querySelector('a[href*="/video/BV"]')
  215. .href.match(/video\/(BV\w+)/)[1],
  216. }));
  217. const currentIdx = videoList.findIndex((v) => v.element === card);
  218. playVideo(currentIdx);
  219. });
  220.  
  221. card.style.position = "relative";
  222. card.appendChild(previewBtn);
  223. card.dataset.processed = true;
  224. }
  225. });
  226. };
  227.  
  228. // 初始化执行
  229. initPreviewContainer();
  230. processCards();
  231.  
  232. // 动态内容监听
  233. new MutationObserver((mutations) => {
  234. mutations.forEach((mutation) => {
  235. if (mutation.addedNodes.length) {
  236. processCards();
  237. }
  238. });
  239. }).observe(document.querySelector("#app"), {
  240. childList: true,
  241. subtree: true,
  242. });
  243.  
  244. // 播放结束检测(需根据实际播放情况调整)
  245. setInterval(() => {
  246. const iframe = document.querySelector("#preview-player");
  247. if (iframe && iframe.contentWindow) {
  248. try {
  249. const player = iframe.contentWindow.document.querySelector("video");
  250. if (player && player.ended) {
  251. playNext();
  252. }
  253. } catch (e) {
  254. // 跨域安全限制
  255. }
  256. }
  257. }, 1000);
  258. })();

QingJ © 2025

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