Greasy Fork镜像 支持简体中文。

bilibili视频合集倒序

bilibili视频合集增加倒序按钮,点击按钮后,合集支持倒序播放,方便从头开始追剧!

  1. // ==UserScript==
  2. // @name bilibili视频合集倒序
  3. // @namespace http://tampermonkey.net/
  4. // @version 0.62
  5. // @description bilibili视频合集增加倒序按钮,点击按钮后,合集支持倒序播放,方便从头开始追剧!
  6. // @author zyb
  7. // @match https://www.bilibili.com/video/*
  8. // @icon https://www.google.com/s2/favicons?sz=64&domain=bilibili.com
  9. // @grant none
  10. // @license MIT
  11. // @require https://update.gf.qytechs.cn/scripts/479598/1311136/MyJSCodeLibrary.js
  12. // ==/UserScript==
  13.  
  14. (async function () {
  15. 'use strict';
  16.  
  17. // Your code here...
  18.  
  19. /**
  20. * bilibili视频合集倒序,支持倒序播放视频
  21. */
  22. class BiliBliVideoCollectionReverse extends MyJSCodeLibrary{
  23.  
  24. // 存储视频合集数据
  25. videoCollectionArr = [[]];
  26. // 当前正在播放的视频合集的下标
  27. videoCollectionArrIndex = 0;
  28. // 下一个播放的视频合集的下标
  29. nextVideoCollectionArrIndex = null;
  30. // 当前正在播放的视频下标
  31. videoIndex = 0;
  32. // 下一个播放的视频dom
  33. nextVideoDom = null;
  34. // 下一个播放的视频下标
  35. nextVideoIndex = null;
  36.  
  37. // bilibili新样式className,方便后续替换
  38. classNameStrObj = {
  39. headDomClassName: ".base-video-sections-v1 .video-sections-head .video-sections-head_second-line",
  40. collectDataDomClassName: ".second-line_left",
  41. subscriptionCollectionBtnDomClassName: ".second-line_right",
  42. videoDomClassName: ".bpx-player-video-wrap video",
  43. videoSectionListClassName: ".video-section-list",
  44. videoEpisodeCardClassName: ".video-section-list .video-episode-card",
  45. }
  46.  
  47. // 正在播放的合集视频className标识
  48. PLAYING_VIDEO_CLASSNAME = "video-episode-card__info-playing";
  49.  
  50. constructor() {
  51. super();
  52. this.init();
  53. }
  54.  
  55. async init() {
  56. // 存储this指针
  57. const _this = this;
  58.  
  59. // 创建css样式
  60. const styleText = `
  61. .base-video-sections-v1 .video-sections-head_second-line .second-line_right {
  62. width: 70px;
  63. height: 24px;
  64. border: 1px solid #00AEEC;
  65. border: 1px solid var(--brand_blue);
  66. text-align: center;
  67. color: #00AEEC;
  68. color: var(--brand_blue);
  69. border-radius: 2px;
  70. background: #F1F2F3;
  71. background: var(--bg3);
  72. outline: none;
  73. cursor: pointer;
  74. }
  75. `;
  76. _this.createStyleFuc(styleText);
  77.  
  78. // 异步获取当前正在播放的视频合集区域的顶部dom节点
  79. const headDom = await _this.getDomByTimeoutAsyncFuc(_this.classNameStrObj.headDomClassName, 3000);
  80. // 区分普通视频和合集视频
  81. if (!headDom) {
  82. console.log("视频不是合集模式");
  83. return;
  84. }
  85. const collectDataDom = headDom.querySelectorAll(_this.classNameStrObj.collectDataDomClassName)[0];
  86. const subscriptionCollectionBtnDom = headDom.querySelectorAll(_this.classNameStrObj.subscriptionCollectionBtnDomClassName)[0];
  87. // 创建倒序按钮
  88. const reverseBtnDom = document.createElement("button");
  89. reverseBtnDom.innerHTML = "倒序";
  90. reverseBtnDom.setAttribute("class", _this.classNameStrObj.subscriptionCollectionBtnDomClassName.slice(1));
  91. reverseBtnDom.onclick = () => {
  92. // 重置下一个播放的视频的数据
  93. _this.nextVideoCollectionArrIndex = null;
  94. _this.nextVideoIndex = null;
  95. _this.nextVideoDom = null;
  96. // 将视频合集倒序处理
  97. _this.reverseVideos();
  98. // 切换按钮
  99. reverseBtnDom.innerHTML = ("倒序" === reverseBtnDom.innerHTML) ? "还原" : "倒序";
  100. };
  101. // 按顺序插入倒序按钮
  102. headDom.innerHTML = "";
  103. headDom.appendChild(collectDataDom);
  104. headDom.appendChild(reverseBtnDom);
  105. headDom.appendChild(subscriptionCollectionBtnDom);
  106.  
  107. // 异步获取当前正在播放的视频dom节点
  108. let videoDom = await _this.getDomByTimeoutAsyncFuc(_this.classNameStrObj.videoDomClassName, 3000);
  109.  
  110. // 监听视频播放结束事件
  111. videoDom.addEventListener("ended", function () { //结束
  112. console.log("播放结束");
  113.  
  114. if (!_this.nextVideoDom) {
  115. return;
  116. }
  117. _this.nextVideoDom.click();
  118. }, false);
  119.  
  120. // 监听视频播放开始事件
  121. videoDom.addEventListener("play", function () { //开始
  122. console.log("开始播放");
  123.  
  124. // 获取视频合集dom,并转为数组
  125. const videoSectionsItemArr = Array.from(document.querySelectorAll(_this.classNameStrObj.videoSectionListClassName));
  126. videoSectionsItemArr.forEach((videoSectionsItem, itemIndex) => {
  127. // 获取当前子项下的.video-section-list合集dom节点,并转为数组
  128. const list = Array.from(videoSectionsItem.querySelectorAll(_this.classNameStrObj.videoEpisodeCardClassName));
  129.  
  130. list.forEach((dom, index) => {
  131. // 判断当前正在播放的视频位置
  132. _this.getItemAndVideoIndex(dom, index, itemIndex);
  133. })
  134. })
  135. // 计算接下来要播放的视频下标
  136. _this.setNextVideoIndex();
  137. }, false);
  138. }
  139.  
  140. /**
  141. * 将视频合集倒序
  142. */
  143. async reverseVideos() {
  144. // 存储this指针
  145. const _this = this;
  146. /**
  147. * 存储在windows中的合集视频数据
  148. * this.__INITIAL_STATE__.videoData.ugc_season.sections[0].episodes
  149. */
  150.  
  151. // 获取视频合集dom,并转为数组
  152. let videoSectionsItemArr = Array.from(document.querySelectorAll(_this.classNameStrObj.videoSectionListClassName));
  153.  
  154. /**
  155. * 若videoSectionsItemArr.length大于1
  156. * 则表明当前合集存在多个子项
  157. */
  158. videoSectionsItemArr.forEach((videoSectionsItem, itemIndex) => {
  159. // 获取当前子项下的.video-section-list合集dom节点,并转为数组
  160. let list = Array.from(videoSectionsItem.querySelectorAll(_this.classNameStrObj.videoEpisodeCardClassName));
  161. // 存储倒序处理后的合集视频dom数组
  162. _this.videoCollectionArr[itemIndex] = list.reverse();
  163. // 清空子项节点,并插入倒序处理后的视频dom节点
  164. videoSectionsItem.innerHTML = "";
  165. _this.videoCollectionArr[itemIndex].forEach((dom, index) => {
  166. // 判断当前正在播放的视频位置
  167. _this.getItemAndVideoIndex(dom, index, itemIndex);
  168. videoSectionsItem.appendChild(dom);
  169. })
  170. // 计算接下来要播放的视频下标
  171. _this.setNextVideoIndex();
  172. })
  173.  
  174. }
  175.  
  176. /**
  177. * 获取当前正在播放的合集子项和视频的下标
  178. * @param {HTMLBodyElement} dom 视频dom节点
  179. * @param {number} index 对应的视频合集下标
  180. * @param {number} itemIndex 合集子项的下标
  181. */
  182. getItemAndVideoIndex(dom, index, itemIndex) {
  183. // 存储this指针
  184. const _this = this;
  185.  
  186. // 判断当前正在播放的视频位置
  187. if (dom.childNodes[0].className.includes(_this.PLAYING_VIDEO_CLASSNAME)) {
  188. // 记录当前正在播放的视频合集下标
  189. _this.videoCollectionArrIndex = itemIndex;
  190. // 记录当前正在播放的视频下标
  191. _this.videoIndex = index
  192. }
  193. }
  194.  
  195. /**
  196. * 计算接下来要播放的视频下标
  197. */
  198. setNextVideoIndex() {
  199. // 存储this指针
  200. const _this = this;
  201.  
  202. /**
  203. * 如果下一个播放的视频下标超出当前视频合集子项内的视频数量
  204. * 则播放下一个合集子项的第一个视频
  205. * 否则播放当前视频合集子项的下一个视频
  206. */
  207. if ((_this.videoIndex + 1) >= _this.videoCollectionArr[_this.videoCollectionArrIndex].length) {
  208.  
  209. /**
  210. * 如果下一个播放的视频子项下标超出当前视频合集子项的数量
  211. * 则停止播放
  212. * 否则播放下一个合集子项的第一个视频
  213. */
  214. if (_this.videoCollectionArrIndex + 1 >= _this.videoCollectionArr.length) {
  215. _this.nextVideoIndex = 0;
  216. _this.nextVideoCollectionArrIndex = 0;
  217. _this.nextVideoDom = null;
  218. } else {
  219. _this.nextVideoIndex = 0;
  220. _this.nextVideoCollectionArrIndex = _this.videoCollectionArrIndex + 1;
  221. }
  222. } else {
  223. _this.nextVideoIndex = _this.videoIndex + 1;
  224. _this.nextVideoCollectionArrIndex = _this.videoCollectionArrIndex;
  225. }
  226.  
  227. _this.nextVideoDom = _this.videoCollectionArr[_this.nextVideoCollectionArrIndex][_this.nextVideoIndex];
  228.  
  229. console.log("----------setNextVideoIndex-----------");
  230. console.log("nextVideoDom", _this.nextVideoDom);
  231. console.log("nextVideoCollectionArrIndex:", _this.nextVideoCollectionArrIndex, ",nextVideoIndex:", _this.nextVideoIndex)
  232. console.log("----------setNextVideoIndex-----------");
  233. }
  234. }
  235.  
  236. let biliBliVideoCollectionReverse = new BiliBliVideoCollectionReverse();
  237. })();

QingJ © 2025

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