百度网盘破解SVIP&&倍速播放&&文稿字幕

百度网盘视频完全破解SVIP。电脑用户使用新版火狐或安插件设置UserAgent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0。iPad用户(我iOS15.7):安装Focus浏览器,安装插件,登录即为Svip。

  1. // ==UserScript==
  2. // @name 百度网盘破解SVIP&&倍速播放&&文稿字幕
  3. // @namespace http://tampermonkey.net/
  4. // @match https://pan.baidu.com/
  5. // @match https://pan.baidu.com/*
  6. // @exclude https://pan.baidu.com/aipan/search
  7. // @grant unsafeWindow
  8. // @grant GM_setValue
  9. // @grant GM_getValue
  10. // @grant GM_addStyle
  11. // @run-at document-start
  12. // @connect pan.baidu.com
  13. // @require https://lib.baomitu.com/hls.js/latest/hls.js
  14. // @version 1.5.4
  15. // @license MIT
  16. // @author Gwen
  17. // @homepageURL https://greasyfork.org/zh-CN/scripts/468982-%E7%99%BE%E5%BA%A6%E7%BD%91%E7%9B%98%E7%A0%B4%E8%A7%A3svip-%E5%80%8D%E9%80%9F%E6%92%AD%E6%94%BE-%E6%96%87%E7%A8%BF%E5%AD%97%E5%B9%95
  18. // @description 百度网盘视频完全破解SVIP。电脑用户使用新版火狐或安插件设置UserAgent:Mozilla/5.0 (Windows NT 10.0; Win64; x64; rv:110.0) Gecko/20100101 Firefox/110.0。iPad用户(我iOS15.7):安装Focus浏览器,安装插件,登录即为Svip。
  19. // ==/UserScript==
  20.  
  21. (function() {
  22.  
  23. GM_addStyle('#app{width:100%!important}.vp-toolsbar{display:none!important}.vp-header{min-width:0!important}.vp-personal-video-play{min-width:0}.vp-personal-home-layout__video{min-width:30vw;padding-top:10px!important;height:80vh!important}.vp-personal-home-layout{padding:0 20px!important}.vp-personal-home-layout .vp-aside{padding-top:0!important}.vp-tabs{min-width:10vw!important}')
  24. let originalAddEventListener = EventTarget.prototype.addEventListener;
  25. let hookAddEventListener = function(...args) {
  26. if (args[0] != "keydown" && args[0] != "keyup" && args[0] != "keypress") {
  27. return originalAddEventListener.apply(this, args);
  28. }
  29. }
  30.  
  31. EventTarget.prototype.addEventListener = hookAddEventListener;
  32. document.addEventListener = hookAddEventListener;
  33. document.documentElement.addEventListener = hookAddEventListener;
  34.  
  35. var settings = {
  36. solve_subtitle: true, //处理文稿字幕
  37. subtitles: null,
  38. subtitle_enabled: false,
  39. subtitleAutoEnable: GM_getValue('subtitleAutoEnable', false), //是否自动开启字幕
  40. longPressRate: 2, //长按加速倍速,Safari受设备影响,我的设备最高只能2倍速
  41. lastPlaybackRate: GM_getValue('lastPlaybackRate', 1),
  42. lastCurrentTime: 0,
  43. lastVideoWidth: GM_getValue('lastVideoWidth', null),
  44. lastTabWidth: GM_getValue('lastTabWidth', null),
  45. resolution: null,
  46. failCountThreshold: 15, //视频加载几秒仍未加载插件则手动发送获取视频m3u8的请求
  47. path: null,
  48. isSvip: null,
  49. adToken: null,
  50. bdstoken: null,
  51. globalVideo: null,
  52. hls: null,
  53. histories: GM_getValue('histories', []),
  54. pdf: null,
  55. m3u8url: null,
  56. capturePath: null, // 要导出的pdf
  57. timePoints: null,
  58. captureHls: null,
  59. }
  60. unsafeWindow.fucksettings = settings
  61. if (location.href.indexOf('https://pan.baidu.com/disk/main') != -1) {
  62. hookRequest()
  63. unsafeWindow.locals.userInfo.vip_identity = '2'
  64. unsafeWindow.locals.userInfo.vip_level = '10'
  65. unsafeWindow.locals.userInfo.svip10_id = '000000'
  66. let fuckLocals = setInterval(() => {
  67. if (unsafeWindow?.locals?.userInfo) {
  68. clearInterval(fuckLocals);
  69. unsafeWindow.locals.userInfo.vip_identity = '21'
  70. unsafeWindow.locals.userInfo.vip_level = '10'
  71. unsafeWindow.locals.userInfo.svip10_id = '000000'
  72. }
  73. }, 100)
  74. function wait() {
  75. let center = document.head && document.body && document.querySelector('.wp-s-header__center')
  76. if (!center) {
  77. setTimeout(wait, 300)
  78. } else {
  79. initPlayHistory()
  80. let historyBtn = document.createElement('a')
  81. historyBtn.href = '#'
  82. historyBtn.innerText = '播放历史'
  83. historyBtn.onclick = e => {
  84. e.preventDefault()
  85. document.querySelector('.history-wrapper').style.display='block';
  86. loadHistories()
  87. }
  88. center.appendChild(historyBtn)
  89. }
  90. }
  91. wait()
  92. return;
  93. } else if (location.href.indexOf('/pfile') == -1) {
  94. hookRequest()
  95. setLocals()
  96. return
  97. }
  98.  
  99. //兼容某些浏览器无GMapi
  100. function GM_setValue(key, value) {
  101. settings[key] = value
  102. if (typeof value === 'string') {
  103. localStorage.setItem(key, value);
  104. } else if (typeof value === 'number' || typeof value === 'boolean') {
  105. localStorage.setItem(key, value.toString());
  106. } else {
  107. localStorage.setItem(key, JSON.stringify(value));
  108. }
  109. }
  110. function GM_getValue(key, defaultValue = null) {
  111. const value = localStorage.getItem(key);
  112. if (value === null || value === undefined) {
  113. return defaultValue;
  114. }
  115. try {
  116. return JSON.parse(value);
  117. } catch (error) {
  118. alert(`Error parsing stored value for key '${key}': ${error}`);
  119. return defaultValue;
  120. }
  121. }
  122. function createElement(tag, clazz, attrs) {
  123. const elem = document.createElement(tag);
  124. elem.className = clazz;
  125. if (attrs) {
  126. for (let key in attrs) {
  127. elem[key] = attrs[key];
  128. }
  129. }
  130. return elem;
  131. }
  132. function throttle(fn, delay) {
  133. var ctx;
  134. var args;
  135. var previous = Date.now();
  136. var later = function () {
  137. fn.apply(ctx, args);
  138. };
  139. return function () {
  140. ctx = this;
  141. args = arguments;
  142. var now = Date.now();
  143. var diff = now - previous - delay;
  144. if (diff >= 0) {
  145. previous = now;
  146. setTimeout(later, delay);
  147. }
  148. };
  149. }
  150.  
  151. var $msg = {success:console.log,error:console.log,info:console.log}
  152. let h0x00=setInterval(()=>{
  153. if(document&&document.head&&document.body) {
  154. clearInterval(h0x00)
  155. function useMessage(){function n(n){for(var o=10,e=0;e<f.length;e++){var t=f[e];if(n&&n===t)break;o+=t.clientHeight+20}return o}function o(o){for(var e=0;e<f.length;e++){if(f[e]===o){f.splice(e,1);break}}o.classList.add(a.hide),f.forEach(function(o){o.style.top=n(o)+"px"})}function e(e){function i(){p.removeEventListener("animationend",i),setTimeout(o,x||t.duration||3e3,p)}function s(){"0"===getComputedStyle(p).opacity&&(p.removeEventListener("transitionend",s),p.remove())}var d=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"info",x=arguments[2],p=r.createElement("div");p.className=a.box+" "+d,p.style.top=n()+"px",p.style.zIndex=c,p.innerHTML='\n <span class="'+a.icon+'"></span>\n <span class="'+a.text+'">'+e+"</span>\n ",c++,f.push(p),r.body.appendChild(p),p.addEventListener("animationend",i),p.addEventListener("transitionend",s)}var t=arguments.length>0&&void 0!==arguments[0]?arguments[0]:{},r=document,i="__"+Math.random().toString(36).slice(2,7),a={box:"msg-box"+i,hide:"hide"+i,text:"msg-text"+i,icon:"msg-icon"+i},s=r.createElement("style");s.textContent=("\n ."+a.box+", ."+a.icon+", ."+a.text+" {\n padding: 0;\n margin: 0;\n box-sizing: border-box;\n }\n ."+a.box+" {\n position: fixed;\n top: 0;\n left: 50%;\n display: flex;\n padding: 12px 16px;\n border-radius: 2px;\n background-color: #fff;\n box-shadow: 0 3px 3px -2px rgba(0,0,0,.2),0 3px 4px 0 rgba(0,0,0,.14),0 1px 8px 0 rgba(0,0,0,.12);\n white-space: nowrap;\n animation: "+a.box+"-move .4s;\n transition: .4s all;\n transform: translate3d(-50%, 0%, 0);\n opacity: 1;\n overflow: hidden;\n }\n ."+a.box+'::after {\n content: "";\n position: absolute;\n left: 0;\n top: 0;\n height: 100%;\n width: 4px;\n }\n @keyframes '+a.box+"-move {\n 0% {\n opacity: 0;\n transform: translate3d(-50%, -100%, 0);\n }\n 100% {\n opacity: 1;\n transform: translate3d(-50%, 0%, 0);\n }\n }\n ."+a.box+"."+a.hide+" {\n opacity: 0;\n /* transform: translate3d(-50%, -100%, 0); */\n transform: translate3d(-50%, -100%, 0) scale(0);\n }\n ."+a.icon+" {\n display: inline-block;\n width: 18px;\n height: 18px;\n border-radius: 50%;\n overflow: hidden;\n margin-right: 6px;\n position: relative;\n }\n ."+a.text+" {\n font-size: 14px;\n line-height: 18px;\n color: #555;\n }\n ."+a.icon+"::after,\n ."+a.icon+'::before {\n position: absolute;\n content: "";\n background-color: #fff;\n }\n .'+a.box+".info ."+a.icon+", ."+a.box+".info::after {\n background-color: #1890ff;\n }\n ."+a.box+".success ."+a.icon+", ."+a.box+".success::after {\n background-color: #52c41a;\n }\n ."+a.box+".warning ."+a.icon+", ."+a.box+".warning::after {\n background-color: #faad14;\n }\n ."+a.box+".error ."+a.icon+", ."+a.box+".error::after {\n background-color: #ff4d4f;\n }\n ."+a.box+".info ."+a.icon+"::after,\n ."+a.box+".warning ."+a.icon+"::after {\n top: 15%;\n left: 50%;\n margin-left: -1px;\n width: 2px;\n height: 2px;\n border-radius: 50%;\n }\n ."+a.box+".info ."+a.icon+"::before,\n ."+a.box+".warning ."+a.icon+"::before {\n top: calc(15% + 4px);\n left: 50%;\n margin-left: -1px;\n width: 2px;\n height: 40%;\n }\n ."+a.box+".error ."+a.icon+"::after, \n ."+a.box+".error ."+a.icon+"::before {\n top: 20%;\n left: 50%;\n width: 2px;\n height: 60%;\n margin-left: -1px;\n border-radius: 1px;\n }\n ."+a.box+".error ."+a.icon+"::after {\n transform: rotate(-45deg);\n }\n ."+a.box+".error ."+a.icon+"::before {\n transform: rotate(45deg);\n }\n ."+a.box+".success ."+a.icon+"::after {\n box-sizing: content-box;\n background-color: transparent;\n border: 2px solid #fff;\n border-left: 0;\n border-top: 0;\n height: 50%;\n left: 35%;\n top: 13%;\n transform: rotate(45deg);\n width: 20%;\n transform-origin: center;\n }\n ").replace(/(\n|\t|\s)*/gi,"$1").replace(/\n|\t|\s(\{|\}|\,|\:|\;)/gi,"$1").replace(/(\{|\}|\,|\:|\;)\s/gi,"$1"),r.head.appendChild(s);var c=t.zIndex||1e4,f=[];return{show:e,info:function(n){e(n,"info")},success:function(n){e(n,"success")},warning:function(n){e(n,"warning")},error:function(n){e(n,"error")}}}
  156. $msg=useMessage();
  157. $msg.success('脚本开始运行')
  158. }
  159. },100)
  160.  
  161. //为解决某些我无法解决的视频加载错误或手机端插件各种bug的循环检错器
  162. var failCount = 0
  163. var failChecker = null
  164. //手动请求视频资源
  165. function fetchVideoM3U8() {
  166. settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
  167. let xhr = new XMLHttpRequest()
  168. let url = `https://pan.baidu.com/api/streaming?app_id=250528&clienttype=0&channel=chunlei&web=1&isplayer=1&check_blue=1&type=M3U8_AUTO_${settings.resolution?settings.resolution:'480'}&trans=&vip=0` +
  169. `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${settings.path}&jsToken=${unsafeWindow.jsToken}`
  170. xhr.open("GET", url)
  171. xhr.manual = true
  172. if (settings.adToken) {
  173. xhr.callback = function() {
  174. $msg.info('开始获取m3u8')
  175. fetchVideoM3U8()
  176. }
  177. }
  178. xhr.send()
  179. }
  180. function fetchResolution() {
  181. let xhr = new XMLHttpRequest()
  182. let url = `https://pan.baidu.com/api/filemetas?clienttype=0&app_id=250528&web=1&channel=chunlei`
  183. let body = `dlink=1&target=${encodeURIComponent('["' + settings.path + '"]')}`
  184. xhr.open("POST", url)
  185. xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded')
  186. xhr.send(body)
  187. }
  188. function startFailChecker() {
  189. if (failChecker != null) {
  190. $msg.error('failChecker已在启动')
  191. return
  192. }
  193. failChecker = setInterval(() => {
  194. if (settings.globalVideo.readyState == 0) { //视频未成功加载
  195. failCount++
  196. if (failCount == settings.failCountThreshold) {
  197. $msg.error('视频未成功加载')
  198. clearInterval(failChecker)
  199. fetchVideoM3U8()
  200. failCount = 0
  201. }
  202. } else {
  203. $msg.success('视频成功加载')
  204. clearInterval(failChecker)
  205. }
  206. }, 1000)
  207. }
  208.  
  209. function init() {
  210. let video = document.querySelector('video')
  211. if (!video) {
  212. console.log('still loading...')
  213. setTimeout(init, 400)
  214. } else if (video.id == 'vjs_video_3_html5_api') {
  215. video.remove()
  216. console.log('%removed beforeplay animation', 'color:blue')
  217. setTimeout(init, 400)
  218. } else {
  219. console.log('%cloaded!', 'color:red')
  220. $msg.success('加载成功')
  221. settings.globalVideo = video
  222. settings.path = new URLSearchParams(new URL(location.href).search).get('path');
  223. settings.isSvip = settings.isSvip
  224. || (document.querySelector('.vp-personal-userinfo__vip-icon')&&document.querySelector('.vp-personal-userinfo__vip-icon').src.indexOf('svip') != -1)
  225. || unsafeWindow?.locals?.is_svip
  226. fetchResolution()
  227. // if (settings.hls == null) {
  228. // bindHls(video.src)
  229. // }
  230.  
  231. video.parentElement.onselectstart = function(){return false;};
  232. video.autoplay = 'true'
  233. var scrubber = document.createElement("div");
  234. scrubber.style = 'text-align: center; width: 100%; z-index: 1000; color: white; font-weight: bold; text-shadow: black 0px 0px 10px;position: absolute;top: 50%;font-size: 30px;'
  235. var speedAlert = document.createElement('div')
  236. speedAlert.innerText = settings.longPressRate + '倍速播放中'
  237. speedAlert.style = 'text-align: center; width: 100%;position: absolute; top: 20px;z-index: 100; font-size: 30px;color: orange; text-shadow: black 0px 0px 20px;'
  238. speedAlert.style.display = 'none'
  239. video.parentElement.appendChild(scrubber);
  240. video.parentElement.appendChild(speedAlert)
  241.  
  242. let scrubbing = false;
  243. let scrubStartX = 0;
  244. let scrubStartTime = 0;
  245. let deltaX = 0
  246.  
  247. function updateScrubber() {
  248. var currentTime = video.currentTime;
  249. var duration = video.duration;
  250. scrubber.textContent = formatTime(currentTime) + " / " + formatTime(duration);
  251. }
  252.  
  253. function formatTime(time) {
  254. var hours = Math.floor(time / 3600);
  255. var minutes = Math.floor((time % 3600) / 60);
  256. var seconds = Math.floor(time % 60);
  257. return (hours > 0 ? hours + ":" : "") + (minutes < 10 ? "0" : "") + minutes + ":" + (seconds < 10 ? "0" : "") + seconds;
  258. }
  259.  
  260. let isLongPress = false
  261. let longPressTimer;
  262. let longPressThreshold = 500; // milliseconds
  263.  
  264. video.addEventListener("touchstart", function(event) {
  265. if (event.touches.length == 1) {
  266. isLongPress = false;
  267. scrubbing = true;
  268. scrubStartX = event.touches[0].pageX;
  269. scrubStartTime = video.currentTime;
  270.  
  271. longPressTimer = setInterval(function() {
  272. GM_setValue('lastPlaybackRate', video.playbackRate)
  273. video.playbackRate = settings.longPressRate;
  274. speedAlert.style.display = 'block'
  275. isLongPress = true;
  276. clearInterval(longPressTimer);
  277. }, longPressThreshold);
  278. }
  279. });
  280.  
  281. video.addEventListener("touchmove", function(event) {
  282. if (scrubbing && event.touches.length == 1 && !isLongPress) {
  283. deltaX = event.touches[0].pageX - scrubStartX;
  284. var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
  285. if (scrubTime < 0) {
  286. scrubTime = 0;
  287. } else if (scrubTime > video.duration) {
  288. scrubTime = video.duration;
  289. }
  290. clearInterval(longPressTimer)
  291. scrubber.style.display = "block";
  292. scrubber.textContent = formatTime(scrubTime) + " / " + formatTime(video.duration);
  293. }
  294. });
  295.  
  296. video.addEventListener("touchend", function(event) {
  297. if (scrubbing && event.touches.length == 0) {
  298. scrubbing = false;
  299. if (!isLongPress) {
  300. deltaX = event.changedTouches[0].pageX - scrubStartX;
  301. if (deltaX != 0) {
  302. var scrubTime = scrubStartTime + deltaX * video.duration / video.clientWidth * 0.08;
  303. if (scrubTime < 0) {
  304. scrubTime = 0;
  305. } else if (scrubTime > video.duration) {
  306. scrubTime = video.duration;
  307. }
  308. video.currentTime = scrubTime;
  309. scrubber.style.display = "none";
  310. }
  311. }
  312.  
  313. clearInterval(longPressTimer);
  314. isLongPress = false
  315. speedAlert.style.display = 'none'
  316. video.playbackRate = settings.lastPlaybackRate;
  317. }
  318. });
  319.  
  320. video.addEventListener("touchcancel", function(event) {
  321. if (scrubbing && event.touches.length == 0) {
  322. scrubbing = false;
  323. scrubber.style.display = "none";
  324.  
  325. clearInterval(longPressTimer);
  326. isLongPress = false
  327. speedAlert.style.display = 'none'
  328. video.playbackRate = settings.lastPlaybackRate;
  329. }
  330. });
  331.  
  332. //播放历史模块
  333. initPlayHistory()
  334. let playHistoryButton = document.createElement('li')
  335. playHistoryButton.className = 'vp-menu-item'
  336. playHistoryButton.innerHTML = `<a class="vp-link" href="#"><span>播放历史</span></a>`
  337. document.querySelector('.vp-menu').appendChild(playHistoryButton)
  338. playHistoryButton.onclick = e => {e.preventDefault();loadHistories();document.querySelector('.history-wrapper').style.display='block';}
  339. historyUpdateCount = 0
  340.  
  341. //FIX: 替换自带progressBar修复手动使用Hls播放高清视频后的点击bug
  342. let progressBarHtml = `<div class="vjs-progress-control vjs-control vp-video-progress-control"><div tabindex="0" class="vjs-progress-holder vjs-slider vjs-slider-horizontal" role="slider" aria-valuenow="0.00" aria-valuemin="0" aria-valuemax="100" aria-label="进度条" aria-valuetext="2:01/-:-">
  343. <div class="vjs-play-progress vjs-slider-bar" aria-hidden="true" style="width: 0%;"><div class="vjs-time-tooltip" aria-hidden="true" style="right: -18.5px;">0:00</div></div>
  344. </div></div>`
  345. let progressParent = document.querySelector('.vjs-progress-control').parentElement
  346. progressParent.children[0].remove()
  347. progressParent.insertAdjacentHTML('afterbegin', progressBarHtml)
  348. let progressHolder = progressParent.children[0].children[0]
  349. let progressBar = progressHolder.children[0]
  350. let progressToolTip = progressBar.children[0]
  351. let subtitleElement = document.querySelector('.vp-video__subtitle-text-first')
  352. let subtitleTab = null
  353. let subtitleWrapper = null
  354. let subtitleContent = null
  355. let subtitleInitTimer = setInterval(() => {
  356. if (!subtitleTab) {
  357. const elements = document.querySelectorAll('.vp-tabs__header-item');
  358. elements.forEach(elem => {
  359. if (elem.innerText.trim() == '文稿') {
  360. console.log('已获取到文稿元素')
  361. subtitleTab = elem
  362. }
  363. })
  364. } else {
  365. if (subtitleTab.className.indexOf('active')) {
  366. if (!subtitleWrapper) {
  367. subtitleWrapper = document.querySelector('.ai-draft__wrap-list')
  368. } else {
  369. console.log('%c加载文稿列表', 'color:pink;')
  370. subtitleContent = subtitleWrapper.parentElement
  371. clearInterval(subtitleInitTimer)
  372. }
  373. }
  374. }
  375. }, 400)
  376. progressHolder.addEventListener('click', updateProgress);
  377. progressHolder.addEventListener('touchstart', updateProgress);
  378.  
  379. subtitleElement.style.fontSize = '24px'
  380. subtitleElement.parentElement.style.height = 'fit-content'
  381. subtitleElement.parentElement.style.left = '50%'
  382. subtitleElement.parentElement.style.top = '80%'
  383. subtitleElement.parentElement.style.maxWidth = '70%'
  384. subtitleElement.parentElement.parentElement.style.position = 'relative'
  385. // function makeDraggableFunction(t,e,n,s){return function(l){var o=parseInt(t.style.left),i=parseInt(t.style.top),p=l.clientX-o,r=l.clientY-i;n&&n(),document.onmousemove=function(n){t.style.left=n.clientX-p+"px",t.style.top=n.clientY-r+"px",e||(parseInt(t.style.left)<=0&&(t.style.left="0px"),parseInt(t.style.top)<=0&&(t.style.top="0px"),parseInt(t.style.left)>=window.innerWidth-parseInt(t.style.width)&&(t.style.left=window.innerWidth-parseInt(t.style.width)+"px"),parseInt(t.style.top)>=window.innerHeight-parseInt(t.style.height)&&(t.style.top=window.innerHeight-parseInt(t.style.height)+"px"))},document.onmouseup=function(){document.onmousemove=null,document.onmouseup=null,s&&s()}}}
  386. function makeDraggableFunction(elem, allowMoveOut, exec, callback) {
  387. let handleMouseDown = function (event) {
  388. let offsetX = event.clientX - elem.getBoundingClientRect().left
  389. let offsetY = event.clientY - elem.getBoundingClientRect().top
  390.  
  391. if (!!exec) {
  392. exec()
  393. }
  394.  
  395. document.onmousemove = function (event) {
  396. let newLeft = event.clientX - offsetX
  397. let newTop = event.clientY - offsetY
  398.  
  399. if (!allowMoveOut) {
  400. // 获取父元素的边界
  401. let parentRect = elem.parentElement.getBoundingClientRect()
  402.  
  403. // 计算元素的右边界和底边界
  404. let rightBoundary = parentRect.right
  405. let bottomBoundary = parentRect.bottom
  406.  
  407. // 调整新位置,确保元素不超出右边界和底边界
  408. newLeft = Math.min(Math.max(newLeft, parentRect.left), rightBoundary - elem.offsetWidth)
  409. newTop = Math.min(Math.max(newTop, parentRect.top), bottomBoundary - elem.offsetHeight)
  410. }
  411.  
  412. elem.style.left = newLeft + 'px'
  413. elem.style.top = newTop + 'px'
  414. }
  415.  
  416. document.onmouseup = function () {
  417. document.onmousemove = null
  418. document.onmouseup = null
  419.  
  420. if (!!callback) {
  421. callback()
  422. }
  423. }
  424. }
  425.  
  426. return handleMouseDown
  427. }
  428. subtitleElement.parentElement.addEventListener('mousedown', makeDraggableFunction(subtitleElement.parentElement, false))
  429.  
  430. // initDraftExport()
  431.  
  432. //点击更新进度条位置
  433. function updateProgress(event) {
  434. var totalWidth = progressHolder.offsetWidth;
  435. var offsetX = 0;
  436. if (event.type === 'click') {
  437. offsetX = event.offsetX;
  438. } else if (event.type === 'touchstart') {
  439. offsetX = event.touches[0].clientX - progressHolder.getBoundingClientRect().left;
  440. }
  441. var percentage = (offsetX / totalWidth) * 100;
  442. progressBar.style.width = percentage + '%';
  443. let currentTime = (percentage / 100) * video.duration;
  444. progressToolTip.innerText = formatTime(currentTime)
  445. video.currentTime = currentTime;
  446. }
  447. progressHolder.addEventListener('touchmove', function(event) {
  448. event.preventDefault();
  449. });
  450. //寻找字幕
  451. function showSubtitle(subtitles, currentTime) {
  452. let left = 0;
  453. let right = subtitles.length - 1;
  454. while (left <= right) {
  455. let middle = Math.floor((left + right) / 2);
  456. let subtitle = subtitles[middle];
  457. if (currentTime >= subtitle.startTime && currentTime <= subtitle.endTime) {
  458. subtitleElement.innerHTML = subtitle.text;
  459. return;
  460. } else if (currentTime < subtitle.startTime) {
  461. right = middle - 1;
  462. } else {
  463. left = middle + 1;
  464. }
  465. }
  466. subtitleElement.innerHTML = '';
  467. }
  468. //video时间变化执行内容
  469. function videoTimeUpdate() {
  470. let currentTime = video.currentTime;
  471. // 更新自定义进度条的位置
  472. let percentage = (currentTime / video.duration) * 100;
  473. progressBar.style.width = percentage + '%';
  474. //如果开启了字幕,显示字幕
  475. if (settings.subtitle_enabled && settings.subtitles) {
  476. if (settings.subtitles) {
  477. showSubtitle(settings.subtitles, this.currentTime);
  478. } else {
  479. subtitleElement.innerText = '字幕正在加载中...'
  480. }
  481. }
  482. historyUpdateCount++
  483. if (historyUpdateCount == 25) {
  484. historyUpdateCount = 0
  485. let lastIdx = settings.path.lastIndexOf('/')
  486. let title = settings.path
  487. if (lastIdx != -1)
  488. title = settings.path.substring(lastIdx + 1)
  489. let history = {
  490. path: settings.path,
  491. title: title,
  492. timestamp: new Date().getTime(),
  493. duration: video.duration,
  494. current: Math.floor(currentTime)
  495. }
  496. updateHistory(history)
  497. }
  498. //破解Svip打开文稿后同步显示字幕位置
  499. if (!settings.isSvip && subtitleTab && subtitleTab.className.indexOf('active') != -1 && subtitleWrapper) {
  500. const paragraphs = subtitleWrapper.children
  501. let currentIndex = 0
  502. for (let i = 0; i < paragraphs.length; i++) {
  503. const paragraph = paragraphs[i]
  504. if (currentTime * 1000 >= paragraph.dataset.starttime) {
  505. currentIndex = i
  506. } else {
  507. break;
  508. }
  509. }
  510. //取消当前高亮
  511. try {
  512. subtitleWrapper.querySelectorAll('.ai-draft__p-sentence--fouce').forEach(node => node.className = 'ai-draft__p-sentence')
  513. } catch(err) {}
  514. const subtitles = settings.subtitles
  515. for (let i = currentIndex * 15; i < (currentIndex + 1) * 15 && i < settings.subtitles.length; i++) {
  516. if (currentTime > subtitles[i].startTime && currentTime < subtitles[i].endTime) {
  517. let paragraph = paragraphs[currentIndex].children[i % 15]
  518. paragraph.className = 'ai-draft__p-sentence ai-draft__p-sentence--fouce'
  519. paragraph.scrollIntoView({ behavior: "smooth", block: "start", inline: "nearest" });
  520. break;
  521. }
  522. }
  523. }
  524. }
  525. video.addEventListener('timeupdate', throttle(videoTimeUpdate, 500));
  526.  
  527. //前进后退倍速字幕
  528. //FIX: 修复替换进度条后键盘控制
  529. let longPressRightArrowTimer = null
  530. document.onkeydown = function(e) {
  531. if (e.key === 'ArrowLeft') {
  532. if (video.currentTime - 15 >= 0) {
  533. video.currentTime -= 15;
  534. } else {
  535. video.currentTime = 0;
  536. }
  537. } else if (e.key === 'ArrowRight') {
  538. if (video.currentTime + 15 < video.duration) {
  539. video.currentTime += 15;
  540. } else {
  541. video.currentTime = video.duration;
  542. }
  543. } else if (e.code === 'Space') {
  544. if (video.paused) {
  545. video.play()
  546. } else {
  547. video.pause()
  548. }
  549. }
  550. };
  551.  
  552. let toolBox = document.createElement('div')
  553. toolBox.style='width:100%;margin:10px 0;display:flex;justify-content:space-between;flex-flow:row wrap;'
  554. toolBox.id = 'toolBox'
  555. let reloadBtn = createButton('重新加载', e => {
  556. fetchVideoM3U8()
  557. })
  558. let subtitleBtn = createButton(settings.subtitle_enabled ? '关闭字幕' : '开启字幕', e => {
  559. if (settings.subtitle_enabled) { //已经开启了,点击关闭
  560. subtitleBtn.textContent = '开启字幕'
  561. document.querySelector('.vp-video__subtitle-text').style.display = 'none'
  562. GM_setValue('subtitleAutoEnable', false)
  563. } else { //点击开启字幕
  564. subtitleBtn.textContent = '关闭字幕'
  565. document.querySelector('.vp-video__subtitle-text').style.display = 'block'
  566. if (!settings.subtitles) {
  567. $msg.info('开始加载字幕文件')
  568. document.querySelector('.vp-video__subtitle-text-first').innerText = '字幕正在加载中...'
  569. document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
  570. }
  571. if (!settings.subtitleAutoEnable && confirm('是否设置自动开启字幕?')) {
  572. GM_setValue('subtitleAutoEnable', true)
  573. }
  574. }
  575. settings.subtitle_enabled = !settings.subtitle_enabled
  576. })
  577. let rewindBtn = createButton('←15s', e => {
  578. if (video.currentTime - 15 >= 0) {
  579. video.currentTime -= 15;
  580. } else {
  581. video.currentTime = 0;
  582. }
  583. })
  584. let forwardBtn = createButton('15s→', e => {
  585. if (video.currentTime + 15 < video.duration) {
  586. video.currentTime += 15;
  587. } else {
  588. video.currentTime = video.duration;
  589. }
  590. })
  591. toolBox.appendChild(reloadBtn)
  592. toolBox.appendChild(subtitleBtn)
  593. toolBox.appendChild(rewindBtn)
  594. toolBox.appendChild(forwardBtn)
  595. let speedBox = document.createElement('select')
  596. speedBox.value = settings.lastPlaybackRate
  597. let speeds = [3, 2, 1.75, 1.5, 1, 0.75]
  598. speeds.forEach(speed => {
  599. const option = document.createElement('option');
  600. option.textContent = speed == 1 ? '正常' : (speed + 'X'); // 设置option的文本值
  601. option.value = speed;
  602. speedBox.appendChild(option);
  603. });
  604. speedBox.addEventListener('change', event => {
  605. const selectedSpeed = event.target.value;
  606. settings.globalVideo.playbackRate = selectedSpeed;
  607. GM_setValue('lastPlaybackRate', selectedSpeed)
  608. });
  609. toolBox.appendChild(speedBox)
  610. let resolutionBox = document.createElement('select')
  611. resolutionBox.id = 'resolution-box'
  612. resolutionBox.addEventListener('change', event => {
  613. const selectedResolution = event.target.value;
  614. if (selectedResolution == '1080') {
  615. alert('1080无法播放好像。。。')
  616. settings.resolution = '720'
  617. } else {
  618. settings.resolution = selectedResolution;
  619. }
  620. fetchVideoM3U8()
  621. })
  622. toolBox.appendChild(resolutionBox)
  623. //导出课件
  624. // let exportPdfBtn = createButton('导出课件', exportPdf1)
  625. // toolBox.appendChild(exportPdfBtn)
  626. video.parentElement.parentElement.parentElement.appendChild(toolBox)
  627. initDraftExport()
  628. if (settings.subtitleAutoEnable) {
  629. setTimeout(() => {
  630. subtitleBtn.click()
  631. }, 1500)
  632. }
  633.  
  634. //FIX: 修改部分css修复宽度小时无法显示全的问题
  635. // setTimeout(() => {
  636. // document.getElementById('app').style.width = '100%'
  637. // document.querySelector('.vp-toolsbar').remove()
  638. // document.querySelector('.vp-header').style.minWidth = '0'
  639. // document.querySelector('.vp-personal-video-play').style.minWidth = '0'
  640. // document.querySelector('.vp-personal-home-layout__video').style.minWidth = '30vw'
  641. // document.querySelector('.vp-personal-home-layout__video').style.paddingTop = '10px'
  642. // document.querySelector('.vp-personal-home-layout__video').style.height = '80vh'
  643. // }, 4000)
  644. // document.querySelector('.vp-personal-home-layout').style.padding = '0 20px'
  645. // document.querySelector('.vp-personal-home-layout .vp-aside').style.paddingTop = '0'
  646. // document.querySelector('.vp-tabs').style.minWidth = '10vw'
  647. if (settings.lastTabWidth) {
  648. document.querySelector('.vp-personal-home-layout .vp-aside').style.width = settings.lastTabWidth
  649. }
  650. function addSplitScreenAdjustment(element1Selector, element2Selector) {
  651. const element1 = document.querySelector(element1Selector);
  652. const element2 = document.querySelector(element2Selector);
  653. let scriptChanging = false;
  654. const handle = document.createElement('div');
  655. handle.style.width = '15px';
  656. handle.style.cursor = 'ew-resize';
  657. handle.style.backgroundColor = '#ccc';
  658. handle.style.margin = '0 5px';
  659. let startX, startWidth1, startWidth2;
  660. const handleMouseDown = (event) => {
  661. startX = event.clientX;
  662. startWidth1 = element1.offsetWidth;
  663. startWidth2 = element2.offsetWidth;
  664. scriptChanging = true
  665. document.addEventListener('mousemove', handleMouseMove);
  666. document.addEventListener('mouseup', handleMouseUp);
  667. };
  668. const handleMouseMove = throttle((event) => {
  669. const dx = event.clientX - startX;
  670. const newWidth1 = startWidth1 + dx;
  671. const newWidth2 = startWidth2 - dx;
  672. element1.style.width = newWidth1 + 'px';
  673. element2.style.width = newWidth2 + 'px';
  674. }, 100);
  675. const handleMouseUp = () => {
  676. document.removeEventListener('mousemove', handleMouseMove);
  677. document.removeEventListener('mouseup', handleMouseUp);
  678. scriptChanging = false
  679. GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
  680. };
  681. handle.addEventListener('mousedown', handleMouseDown);
  682. const handleTouchStart = (event) => {
  683. startX = event.touches[0].clientX;
  684. startWidth1 = element1.offsetWidth;
  685. startWidth2 = element2.offsetWidth;
  686. scriptChanging = true
  687. document.addEventListener('touchmove', handleTouchMove);
  688. document.addEventListener('touchend', handleTouchEnd);
  689. };
  690. const handleTouchMove = throttle((event) => {
  691. const dx = event.touches[0].clientX - startX;
  692. const newWidth1 = startWidth1 + dx;
  693. const newWidth2 = startWidth2 - dx;
  694. element1.style.width = newWidth1 + 'px';
  695. element2.style.width = newWidth2 + 'px';
  696. }, 100);
  697. const handleTouchEnd = () => {
  698. document.removeEventListener('touchmove', handleTouchMove);
  699. document.removeEventListener('touchend', handleTouchEnd);
  700. scriptChanging = false
  701. GM_setValue('lastTabWidth', '"' + element2.style.width + '"')
  702. };
  703. handle.addEventListener('touchstart', handleTouchStart);
  704. element2.parentNode.insertBefore(handle, element2);
  705. //防止百度网盘自动调整他的宽度
  706. var observer = new MutationObserver(function(mutations) {
  707. mutations.forEach(function(mutation) {
  708. const oldStyle = mutation.oldValue;
  709. if (!scriptChanging) {
  710. observer.disconnect()
  711. setTimeout(() => {
  712. element2.style = oldStyle;
  713. observer.observe(element2, {attributes: true, attributeOldValue: true})
  714. }, 50)
  715. }
  716. });
  717. });
  718. observer.observe(element2, { attributes: true, attributeOldValue: true });
  719. }
  720. addSplitScreenAdjustment('main', '.vp-personal-home-layout .vp-aside')
  721.  
  722. let rateOptions = document.querySelector('.vp-video__control-bar--playback-rates').children
  723. for (let i = 0; i < rateOptions.length; i++) {
  724. let option = rateOptions[i]
  725. if (option.classList[1] != 'is-svip-guide') {
  726. option.onclick = function(e) {
  727. e.stopPropagation()
  728. GM_setValue('lastPlaybackRate', Number.parseFloat(option.innerText.replace('X', '')))
  729. video.playbackRate = settings.lastPlaybackRate
  730. }
  731. }
  732. }
  733.  
  734. //双击暂停
  735. let container = video.parentElement;
  736. let pauseThreshold = 500; // milliseconds
  737. let lastClickTime = 0;
  738. let pauseTimer;
  739. container.addEventListener("touchstart", function(event) {
  740. let currentTime = new Date().getTime();
  741. if (currentTime - lastClickTime < pauseThreshold) {
  742. clearTimeout(pauseTimer);
  743. if (video.paused) {
  744. video.play();
  745. } else {
  746. video.pause();
  747. }
  748. } else {
  749. lastClickTime = currentTime;
  750. pauseTimer = setTimeout(function() {
  751. lastClickTime = 0;
  752. }, pauseThreshold);
  753. }
  754. });
  755.  
  756. //FIX: 替换自带全屏修复Focus浏览器全屏使用iOS默认播放器
  757. let vpVideo = document.querySelector('.vp-video')
  758. let controllBar = document.querySelector('.vp-video__control-bar--setup')
  759. controllBar.children[4].remove()
  760. controllBar.insertAdjacentHTML('beforeend', `<div class="vp-video__control-bar--button-group">
  761. <div class="vp-video__control-bar--button is-icon">
  762. <i class="u-icon-screen"></i></div></div>`)
  763. let fullScreenBtn = controllBar.children[4]
  764. let fullScreenIcon = fullScreenBtn.children[0].children[0]
  765. fullScreenBtn.onclick = e => {
  766. if (fullScreenIcon.className == 'u-icon-screen') {
  767. //点击全屏
  768. vpVideo.style.zIndex = '99999'
  769. vpVideo.style.position = 'fixed';
  770. vpVideo.style.top = '0';
  771. vpVideo.style.left = '0';
  772. fullScreenIcon.className = 'u-icon-exit-screen'
  773. } else {
  774. vpVideo.style.position = 'unset';
  775. fullScreenIcon.className = 'u-icon-screen'
  776. }
  777. }
  778.  
  779. //准备完毕,开始检查视频状态
  780. startFailChecker()
  781. }
  782. }
  783.  
  784. function createButton(textContent, callback) {
  785. let btn = document.createElement('div')
  786. btn.style = 'padding: 10px 10px; text-align: center; background: rgb(6, 167, 255); color: white;font-size: 14px;cursor:pointer'
  787. btn.textContent = textContent
  788. if (callback) {
  789. btn.onclick = e => {
  790. callback(e)
  791. }
  792. }
  793. return btn;
  794. }
  795.  
  796. function formatPlayTime(timestamp) {
  797. let current = new Date().getTime()
  798. let gap = Math.floor((current - timestamp) / 1000)
  799. if (gap < 60) {
  800. return `${gap}秒前`
  801. } else if (gap < 60 * 60) {
  802. return `${Math.floor(gap/60)}分钟前`
  803. } else if (gap < 60 * 60 * 24) {
  804. return `${Math.floor(gap/60/60)}小时前`
  805. } else {
  806. return new Date(timestamp).toLocaleString()
  807. }
  808. }
  809. function initPlayHistory() {
  810. let style = document.createElement('style')
  811. style.textContent = '.history-wrapper{display:none;background-color:#fefefe;position:fixed;z-index:99999;top:40%;left:50%;transform:translate(-50%,-50%);padding:5px 10px 10px;border:1px solid #888;width:420px;}.history-list::-webkit-scrollbar{width:4px;}.history-list::-webkit-scrollbar-track{background-color:#f1f1f1;}.history-list::-webkit-scrollbar-thumb{background-color:#888888;}.history-list{min-height:100px;max-height:300px;overflow-y:auto;scrollbar-width:thin;}.history{padding:8px 4px;transition:.2s;cursor:pointer;border-bottom:1px dashed lightgray;}.history:hover{background-color:rgb(240,240,240);}.history-time{color:rgb(100,100,100);font-size:small;}.history-title{font-size:16px;}.history-progress-bar{width:280px;height:6px;background-color:#f1f1f1;border-radius:10px;overflow:hidden;display:inline-block;}.history-progress{width:0%;height:100%;background-color:#007bff;transition:width 0.3s ease-in-out;}.close{color:#aaa;float:right;font-size:28px;font-weight:bold;}.close:hover,.close:focus{color:black;text-decoration:none;cursor:pointer;}.history-end{color:rgb(150,150,150);text-align:center;font-size:small;}';
  812. document.head.append(style)
  813. let historyWrapper = document.createElement('div')
  814. historyWrapper.className = 'history-wrapper'
  815. historyWrapper.innerHTML = `<span style="float:left;margin-top: 5px;font-weight: bold;">播放历史(保留20条)</span>
  816. <span class="close" onclick="closeModal()">&times;</span>
  817. <div style="clear:both;"></div>
  818. <div class="history-list"></div>`
  819. historyWrapper.querySelector('.close').onclick = function(e) {
  820. historyWrapper.style.display = 'none'
  821. }
  822. document.body.append(historyWrapper)
  823. var existingHistoryIndex = settings.histories.findIndex(function(item) {
  824. return item.path === settings.path;
  825. });
  826. if (existingHistoryIndex != -1 && settings.globalVideo) {
  827. settings.globalVideo.currentTime = settings.histories[existingHistoryIndex].current
  828. }
  829. loadHistories()
  830. }
  831. function loadHistories() {
  832. let historyList = document.querySelector('.history-list')
  833. historyList.innerHTML = ''
  834. for (let i = settings.histories.length - 1; i >= 0; i--) {
  835. let history = settings.histories[i]
  836. let historyElem = document.createElement('div')
  837. historyElem.className = 'history'
  838. historyList.append(historyElem)
  839. historyElem.innerHTML = `<div class="history-time">${formatPlayTime(history.timestamp)}</div>
  840. <div class="history-title">${history.title}</div>
  841. <div class="history-progress-bar">
  842. <div class="history-progress" style="width:${100*history.current/history.duration}%"></div>
  843. </div>
  844. <span style="color:rgb(100,100,100);font-size:12px;">观看至${formatTime(history.current)}</span>`
  845. historyElem.setAttribute('path', history.path)
  846. }
  847. historyList.onclick = function(e) {
  848. const elem = event.target.closest('.history');
  849. if (elem) {
  850. const path = elem.getAttribute('path')
  851. if (confirm('跳转视频' + path + '?')) {
  852. location.href = 'https://pan.baidu.com/pfile/video?path=' + encodeURIComponent(path)
  853. }
  854. }
  855. }
  856. historyList.innerHTML += `<div class="history-end">—————— 已经到底了哦 ——————</div>`
  857. }
  858. function updateHistory(history) {
  859. const histories = settings.histories;
  860. var existingHistoryIndex = histories.findIndex(function(item) {
  861. return item.path === history.path;
  862. });
  863. if (existingHistoryIndex !== -1) {
  864. histories.splice(existingHistoryIndex, 1)[0];
  865. histories.push(history);
  866. } else {
  867. histories.push(history);
  868. if (histories.length > 20) {
  869. histories.splice(0, 1)
  870. }
  871. }
  872. GM_setValue('histories', histories);
  873. }
  874.  
  875. function hookRequest() {
  876. var originOpen = XMLHttpRequest.prototype.open;
  877. XMLHttpRequest.prototype.open = function (method, url) {
  878. // console.log(`%c【${method}】:${url}`, 'color:blue;')
  879. if (url.indexOf('netdisk-subtitle') != -1 && settings.solve_subtitle) { //获取字幕信息
  880. this.addEventListener('readystatechange', function() {
  881. if (this.readyState == 4) {
  882. var blobData = this.response;
  883. var reader = new FileReader();
  884. reader.onloadend = function() {
  885. var textContent = reader.result;
  886. let arr = textContent.split('\n')
  887. setTimeout(() => {
  888. let wrapper = document.querySelector('.ai-draft__wrap-list')
  889. try {
  890. document.querySelector('.ai-draft__wrap-content').style = 'padding-right:12px!important;'
  891. document.querySelector('.ai-draft__wrap-content').contentEditable = true
  892. document.querySelector('.ai-draft__filter').remove()
  893. document.querySelector('.ai-draft__svip-guide').remove()
  894. } catch(err) {
  895. }
  896. let subtitles = []
  897. let paragraph
  898. console.log('开始解析字幕')
  899. for (let i = 1; i < arr.length / 4; i++) {
  900. if (arr[i * 4] == '')
  901. break;
  902. let lines = [arr[i * 4], arr[i * 4 + 1], arr[i * 4 + 2]]
  903. let timeParts = lines[1].split(' --> ');
  904. let startTime = srtTimeToSeconds(timeParts[0].trim());
  905. let endTime = srtTimeToSeconds(timeParts[1].trim());
  906. if (!settings.isSvip && i >= 15 * 3 + 1) {
  907. if (i % 15 == 1) {
  908. paragraph = document.createElement('p')
  909. paragraph.className = 'ai-draft__p-paragraph'
  910. paragraph.setAttribute('data-starttime', startTime * 1000)
  911. } else if (i % 15 == 0) {
  912. wrapper.appendChild(paragraph)
  913. }
  914. let span = document.createElement('span')
  915. span.className = 'ai-draft__p-sentence'
  916. span.setAttribute('data-index', i - 1)
  917. span.innerText = lines[2]
  918. span.onclick = e => {
  919. settings.globalVideo.currentTime = startTime
  920. }
  921. paragraph.appendChild(span)
  922. }
  923. subtitles.push({
  924. index: i - 1,
  925. startTime: startTime,
  926. endTime: endTime,
  927. text: lines[2]
  928. })
  929. }
  930. settings.subtitles = subtitles
  931. }, 1000)
  932. };
  933. reader.readAsText(blobData);
  934. }
  935. });
  936. originOpen.apply(this, arguments);
  937. } else if (url.indexOf('/api/loginStatus') != -1) { //伪造svip信息
  938. this.addEventListener('readystatechange', function() {
  939. if (this.readyState == 4) {
  940. let res = JSON.parse(this.responseText)
  941. settings.isSvip = res.login_info.vip_type == '21'
  942. res.login_info.vip_type = '21'
  943. res.login_info.vip_identity = '21'
  944. res.login_info.vip_level = 10
  945. res.login_info.vip_point = 99999
  946. res.login_info.svip10_id = '000000'
  947. res.login_info.username = 'sout(\'GwenCrack\')'
  948. settings.bdstoken = res.login_info.bdstoken
  949. Object.defineProperty(this, "responseText", {
  950. writable: true,
  951. });
  952. this.responseText = JSON.stringify(res)
  953. }
  954. })
  955. originOpen.apply(this, arguments);
  956. } else if (url.indexOf('/user/info') != -1) {
  957. this.addEventListener('readystatechange', function() {
  958. if (this.readyState == 4) {
  959. let res = {}
  960. if (this.responseType == 'json') {
  961. res = this.response
  962. } else {
  963. res = JSON.parse(this.responseText)
  964. }
  965. res.user_info.is_vip = 1
  966. res.user_info.is_svip = 1
  967. res.user_info.is_plus_buy = 1
  968. if (this.responseType == 'json') {
  969. Object.defineProperty(this, "responseText", {
  970. writable: true,
  971. });
  972. this.responseText = JSON.stringify(res)
  973. } else {
  974. Object.defineProperty(this, "responseText", {
  975. writable: true,
  976. });
  977. this.response = res
  978. }
  979. }
  980. })
  981. originOpen.apply(this, arguments);
  982. } else if (url.indexOf('/membership/user') != -1) {
  983. this.addEventListener('readystatechange', function() {
  984. if (this.readyState == 4) {
  985. let res = {}
  986. if (this.responseType == 'json') {
  987. res = this.response
  988. } else {
  989. res = JSON.parse(this.responseText)
  990. }
  991. res.reminder = {
  992. "svip": {
  993. "leftseconds": 999999999999,
  994. "nextState": "normal"
  995. }
  996. }
  997. res.level_info = {
  998. "current_value": 12090,
  999. "current_level": 10,
  1000. "history_value": 11830,
  1001. "history_level": 10,
  1002. "svip10_id": "000000",
  1003. "last_manual_collection_time": 0
  1004. }
  1005. res.product_infos = [{
  1006. "product_id": "",
  1007. "start_time": 1685635199,
  1008. "end_time": 2547483648,
  1009. "buy_time": 0,
  1010. "cluster": "vip",
  1011. "detail_cluster": "svip",
  1012. "auto_upgrade_to_svip": 0,
  1013. "product_name": "svip2_nd",
  1014. "status": 0,
  1015. "function_num": 0,
  1016. "buy_description": "",
  1017. "product_description": "",
  1018. "cur_svip_type": "month"
  1019. }]
  1020. res.current_product = {
  1021. "cluster": "vip",
  1022. "detail_cluster": "svip",
  1023. "product_type": "vip2_1m_auto",
  1024. "product_id": "12187135090581539740"
  1025. }
  1026. res.current_product_v2 = {
  1027. "cluster": "vip",
  1028. "detail_cluster": "svip",
  1029. "product_type": "vip2_1m_auto",
  1030. "product_id": "12187135090581539740"
  1031. }
  1032. if (this.responseType == 'json') {
  1033. Object.defineProperty(this, "response", {
  1034. writable: true,
  1035. });
  1036. this.response = res
  1037. } else {
  1038. Object.defineProperty(this, "responseText", {
  1039. writable: true,
  1040. });
  1041. this.responseText = JSON.stringify(res)
  1042. }
  1043. }
  1044. })
  1045. originOpen.apply(this, arguments);
  1046. } else if (url.indexOf('/api/streaming') != -1 && url.indexOf('M3U8_SUBTITLE_SRT') == -1) { //获取视频m3u8接口
  1047. let modifiedUrl = url.replace(/vip=2/, 'vip=0')
  1048. // .replace(/M3U8_.*?&/, 'M3U8_AUTO_1080&')
  1049. if (settings.adToken) {
  1050. modifiedUrl += ('&adToken=' + encodeURIComponent(settings.adToken))
  1051. settings.adToken = null
  1052. }
  1053. originOpen.call(this, method, modifiedUrl, arguments[2]);
  1054. this.addEventListener('readystatechange', function() {
  1055. if (this.readyState == 4) {
  1056. if (this.status != 200) {
  1057. $msg.error('加载失败!!!!')
  1058. return
  1059. }
  1060. if (this.responseText[0] == '{') {
  1061. let res = JSON.parse(this.responseText)
  1062. settings.adToken = res.adToken
  1063. res.ltime = 0.001
  1064. res.adTime = 0.001
  1065. Object.defineProperty(this, "responseText", {
  1066. writable: true,
  1067. });
  1068. this.responseText = JSON.stringify(res)
  1069.  
  1070. if (settings.isSvip) {
  1071. settings.lastCurrentTime = settings.globalVideo ? settings.globalVideo.currentTime : 0
  1072. let xhr = new XMLHttpRequest()
  1073. let url = `https://pan.baidu.com/api/streaming?app_id=250528&clienttype=0&channel=chunlei&web=1&isplayer=1&check_blue=1&type=M3U8_AUTO_${settings.resolution?settings.resolution:'480'}&trans=&vip=0` +
  1074. `&bdstoken=${settings.bdstoken||unsafeWindow.locals.bdstoken}&path=${encodeURIComponent(settings.path)}&jsToken=${unsafeWindow.jsToken}`
  1075. xhr.open("GET", url, false)
  1076. xhr.send()
  1077. this.responseText = xhr.responseText.replace(/https.*?\//, 'https://nv0.baidupcs.com/')
  1078. } else if (this.callback) {
  1079. this.callback()
  1080. }
  1081. } else {
  1082. let m3u8Content = this.responseText
  1083. let blob = new Blob([this.responseText], { type: 'application/vnd.apple.mpegurl' });
  1084. let url = URL.createObjectURL(blob);
  1085. bindHls(url)
  1086. settings.m3u8url = url
  1087. }
  1088. }
  1089. })
  1090. } else if (url.indexOf('/api/streaming') != -1 && url.indexOf('SUBTITLE_SRT') != -1) {
  1091. this.addEventListener('readystatechange', function() {
  1092. if (this.readyState == 4) {
  1093. let res = this.responseText
  1094. Object.defineProperty(this, "responseText", {
  1095. writable: true,
  1096. });
  1097. this.responseText = res.replace(/https:\/\/.*?\//, 'https://nv0.baidupcs.com/')
  1098. }
  1099. })
  1100. originOpen.apply(this, arguments);
  1101. } else if (url.indexOf('/api/filemetas') != -1) {
  1102. this.addEventListener('readystatechange', function() {
  1103. if (this.readyState == 4) {
  1104. let res = JSON.parse(this.responseText)
  1105. if (res.info.length != 1)
  1106. return
  1107. let resolution = res.info[0].resolution
  1108. console.log('分辨率'+ resolution)
  1109. let resolutionOptions = []
  1110. let match = false
  1111. switch(resolution) {
  1112. case 'width:1920,height:1080':
  1113. match = true
  1114. resolutionOptions.push('1080')
  1115. case 'width:1280,height:720':
  1116. match = true
  1117. resolutionOptions.push('720')
  1118. case 'width:720,height:480':
  1119. match = true
  1120. resolutionOptions.push('480')
  1121. default:
  1122. resolutionOptions.push('360');
  1123. }
  1124. if (!match) {
  1125. resolutionOptions = ['720', '480', '360']
  1126. }
  1127. console.log(resolutionOptions)
  1128. let waitTimer = setInterval(() => {
  1129. let box = document.getElementById('resolution-box')
  1130. if (box) {
  1131. clearInterval(waitTimer)
  1132. box.innerHTML = ''
  1133. resolutionOptions.forEach(resolution => {
  1134. const option = document.createElement('option')
  1135. option.textContent = resolution + 'P'
  1136. option.value = resolution
  1137. box.appendChild(option)
  1138. })
  1139. }
  1140. }, 400)
  1141. }
  1142. })
  1143. originOpen.apply(this, arguments);
  1144. } else if (url.indexOf('/msg/streaming') != -1 || url.indexOf('/share/streaming') != -1) {
  1145. this.addEventListener('readystatechange', function() {
  1146. if (this.readyState == 4) {
  1147. if (this.responseText[0] != '{')
  1148. return
  1149. let res = JSON.parse(this.responseText)
  1150. res.ltime = 0.000001
  1151. res.adTime = 0.000001
  1152. Object.defineProperty(this, 'responseText', {
  1153. writable: true,
  1154. })
  1155. this.responseText = JSON.stringify(res)
  1156. }
  1157. })
  1158. originOpen.apply(this, arguments);
  1159. } else if (url.indexOf('/aitrans/ppt/get') != -1) {
  1160. const parseTimeToSeconds = (timeStr) => {
  1161. let [h, m, s] = timeStr.split(":");
  1162. return parseInt(h)*60*60 + parseInt(m)*60 + parseInt(s);
  1163. }
  1164. this.addEventListener('readystatechange', function() {
  1165. if (this.readyState == 4) {
  1166. console.log("课件:" + this.responseText);
  1167. let res = JSON.parse(this.responseText);
  1168. console.log(res);
  1169. let list = res.data.list;
  1170. if (!list || list.length == 0) {
  1171. $msg.error("没有pdf,等待生成或者别生成了");
  1172. return;
  1173. }
  1174. const timePoints = [];
  1175. for (let item of list) {
  1176. for (let time of item.img_timestamp) {
  1177. console.log(time);
  1178. timePoints.push(parseTimeToSeconds(time))
  1179. }
  1180. }
  1181. console.log(timePoints)
  1182. settings.timePoints = timePoints;
  1183. showPdf();
  1184. }
  1185. })
  1186. originOpen.apply(this, arguments);
  1187. } else {
  1188. originOpen.apply(this, arguments);
  1189. }
  1190.  
  1191. /*
  1192. else if (url.indexOf('/ppt/common') != -1) { // 查询ppt生成情况接口
  1193. this.addEventListener('readystatechange', function() {
  1194. if (this.readyState == 4) {
  1195. console.log("课件:" + this.responseText)
  1196. let res = JSON.parse(this.responseText)
  1197. res.data.rights_info.residue_count = 10
  1198. res.data.rights_info.vip_type = 4
  1199. res.data.rights_info.strategy_flag = 1
  1200. if (url.indexOf('method=apply') != -1) {
  1201. res.data.apply_status = 200
  1202. }
  1203. Object.defineProperty(this, 'responseText', {
  1204. writable: true
  1205. })
  1206. Object.defineProperty(this, 'response', {
  1207. writable: true
  1208. })
  1209. this.response = JSON.stringify(res)
  1210. this.responseText = JSON.stringify(res)
  1211. console.log(res)
  1212. }
  1213. })
  1214. originOpen.apply(this, arguments);
  1215. }
  1216. */
  1217. }
  1218. }
  1219.  
  1220. function bindHls(url) {
  1221. if (!Hls.isSupported()) {
  1222. $msg.error('浏览器不支持播放')
  1223. return
  1224. }
  1225. if (settings.hls) {
  1226. try {
  1227. settings.hls.destroy()
  1228. settings.hls = null
  1229. } catch(err) {
  1230. console.error(err)
  1231. }
  1232. }
  1233. settings.hls = new Hls({
  1234. autoStartLoad: true,
  1235. autoplay: true
  1236. })
  1237. let hls = settings.hls
  1238. let video = settings.globalVideo
  1239. let vpError = document.querySelector('.vp-error')
  1240. if (vpError) {
  1241. vpError.remove()
  1242. }
  1243. hls.on(Hls.Events.MEDIA_ATTACHED, function() {
  1244. hls.loadSource(url);
  1245. });
  1246. hls.on(Hls.Events.MANIFEST_PARSED, function(event, data) {
  1247. console.log('上次加载到' + settings.lastCurrentTime)
  1248. video.currentTime = settings.lastCurrentTime
  1249. video.play();
  1250. video.playbackRate = settings.lastPlaybackRate
  1251. let checkDurationTimer = setInterval(() => {
  1252. if (video.readyState > 0) {
  1253. document.querySelector('.vp-video__control-bar--play-time-all>div').innerText = formatTime(video.duration)
  1254. clearInterval(checkDurationTimer)
  1255. }
  1256. }, 100)
  1257. });
  1258. hls.attachMedia(video);
  1259. }
  1260. function srtTimeToSeconds(timeString) {
  1261. var timeParts = timeString.split(':');
  1262. var hours = parseInt(timeParts[0]);
  1263. var minutes = parseInt(timeParts[1]);
  1264. var secondsAndMilliseconds = timeParts[2].split('.');
  1265. var seconds = parseInt(secondsAndMilliseconds[0]);
  1266. var milliseconds = parseInt(secondsAndMilliseconds[1]);
  1267. var totalSeconds = (hours * 3600) + (minutes * 60) + seconds + (milliseconds / 1000);
  1268. return totalSeconds;
  1269. }
  1270.  
  1271. function formatTime(totalSeconds, requireMil = false) {
  1272. var hours = Math.floor(totalSeconds / 3600);
  1273. var minutes = Math.floor((totalSeconds % 3600) / 60);
  1274. var seconds = Math.floor(totalSeconds % 60);
  1275. var formattedTime = hours.toString().padStart(2, '0') + ':' +
  1276. minutes.toString().padStart(2, '0') + ':' +
  1277. seconds.toString().padStart(2, '0');
  1278. if (requireMil) {
  1279. formattedTime += ',' + (seconds % 1).toFixed(3).substring(2)
  1280. }
  1281. return formattedTime;
  1282. }
  1283. function copyToClipboard(txt){
  1284. if (navigator.clipboard?.writeText)
  1285. navigator.clipboard.writeText(txt)
  1286. else {
  1287. input = document.createElement('textarea')
  1288. input.setAttribute('readonly', 'readonly')
  1289. input.value = txt
  1290. document.body.appendChild(input)
  1291. input.select()
  1292. if (document.execCommand('copy'))
  1293. document.execCommand('copy')
  1294. document.body.removeChild(input)
  1295. }
  1296. }
  1297. function initDraftExport() {
  1298. function getDefaultFilename(ext) {
  1299. var videoNameElement = document.querySelector('div.vp-video-page-card span.is-playing.vp-video-page-card__video-name');
  1300. if (videoNameElement) {
  1301. var originalFilename = videoNameElement.innerText.trim();
  1302. var newFilename = originalFilename.replace(/\.[^/.]+$/, '') + ext; // 去掉原始文件名的后缀,并添加新的后缀名
  1303. return newFilename;
  1304. }
  1305. return 'subtitle' + ext;
  1306. }
  1307. let toolBox = document.getElementById('toolBox')
  1308. // 创建复制字幕按钮
  1309. function createDraftButton(id, textContent, left, callback) {
  1310. const btn = document.createElement('button');
  1311. btn.id = id;
  1312. btn.innerText = textContent;
  1313. // btn.style = `position:fixed;left:${left};bottom:3px;z-index:9999;padding:10px;background:#fff;border:1px solid #ccc;cursor:pointer;`
  1314. btn.style = 'padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'
  1315. // 复制字幕按钮点击事件处理函数
  1316. btn.addEventListener('click', function() {
  1317. if (!settings.subtitles) {
  1318. $msg.info('视频文稿未加载,开始加载...')
  1319. document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
  1320. setTimeout(() => {
  1321. document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
  1322. }, 500)
  1323. return
  1324. }
  1325. callback()
  1326. });
  1327. toolBox.append(btn)
  1328. }
  1329. createDraftButton('copySubtitleBtn', '复制字幕', '40px', function() {
  1330. const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
  1331. const subtitleText = [];
  1332. for (let i = 0; i < subtitleElements.length; i++) {
  1333. subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
  1334. }
  1335. copyToClipboard(subtitleText.join('\n\n')); // 将字幕数组以空行连接起来并返回
  1336. $msg.success('字幕已复制')
  1337. })
  1338. createDraftButton('exportToDocBtn', '导出文稿doc', '120px', function() {
  1339. $msg.info('正在导出文稿...')
  1340. const subtitleElements = document.querySelectorAll('.ai-draft__wrap-list p.ai-draft__p-paragraph'); // 获取所有段落元素
  1341. const subtitleText = [];
  1342. for (let i = 0; i < subtitleElements.length; i++) {
  1343. subtitleText.push(subtitleElements[i].innerText.trim()); // 将每个段落的文本添加到字幕数组中
  1344. }
  1345. const subtitle = subtitleText.join('\n\n'); // 获取字幕内容
  1346. const filename = getDefaultFilename('.doc');
  1347. var blob = new Blob([subtitle], {type: 'text/plain;charset=utf-8'});
  1348. var downloadLink = document.createElement("a");
  1349. downloadLink.href = URL.createObjectURL(blob);
  1350. downloadLink.download = filename;
  1351. document.body.appendChild(downloadLink);
  1352. downloadLink.click();
  1353. document.body.removeChild(downloadLink);
  1354. URL.revokeObjectURL(downloadLink.href);
  1355. //saveAs(blob, filename); // 使用FileSaver.js保存文件
  1356. $msg.success('导出成功')
  1357. })
  1358. createDraftButton('exportToSrtBtn', '导出字幕srt', '220px', function() {
  1359. $msg.info('正在导出文稿...')
  1360. const blobArray = [];
  1361. settings.subtitles.forEach(function(subtitle) {
  1362. const srtText =
  1363. (subtitle.index + 1) + "\n" +
  1364. formatTime(subtitle.startTime, true) + " --> " + formatTime(subtitle.endTime, true) + "\n" +
  1365. subtitle.text + "\n\n";
  1366. // console.log(srtText)
  1367. const srtBlob = new Blob([srtText], { type: "text/plain;charset=utf-8" });
  1368. blobArray.push(srtBlob);
  1369. });
  1370. var combinedBlob = new Blob(blobArray, { type: "text/plain;charset=utf-8" });
  1371. var downloadLink = document.createElement("a");
  1372. downloadLink.href = URL.createObjectURL(combinedBlob);
  1373. downloadLink.download = getDefaultFilename('.srt');
  1374. document.body.appendChild(downloadLink);
  1375. downloadLink.click();
  1376. document.body.removeChild(downloadLink);
  1377. URL.revokeObjectURL(downloadLink.href);
  1378. //saveAs(combinedBlob, getDefaultFilename('.srt'));
  1379. $msg.success('导出成功')
  1380. })
  1381. }
  1382.  
  1383. hookRequest()
  1384. init()
  1385. function setLocals() {
  1386. let localsTimer = setInterval(() => {
  1387. if (!unsafeWindow.locals) return
  1388. clearInterval(localsTimer)
  1389. console.log('设置window.locas', unsafeWindow.locals)
  1390. if (unsafeWindow.locals.userInfo) {
  1391. unsafeWindow.locals.userInfo.vip_level = 8
  1392. unsafeWindow.locals.userInfo.vip_identity = 21
  1393. unsafeWindow.locals.userInfo.username = "GwenCrackヾ(-_-;)"
  1394. } else if(unsafeWindow.locals.mset) {
  1395. unsafeWindow.locals.mset({
  1396. 'is_vip': 1,
  1397. 'is_svip': 1,
  1398. 'vip_level': 8,
  1399. 'show_vip_ad': 0
  1400. })
  1401. } else {
  1402. unsafeWindow.locals.vip_level = 8
  1403. unsafeWindow.locals.is_vip = 1
  1404. unsafeWindow.locals.is_svip = 1
  1405. unsafeWindow.locals.is_evip = 0
  1406. unsafeWindow.locals.show_vip_ad = 0
  1407. }
  1408. }, 100)
  1409. }
  1410. setLocals()
  1411. let lastUrl = location.href
  1412. setInterval(() => {
  1413. if (lastUrl != location.href) {
  1414. lastUrl = location.href
  1415. console.log('%cURL变化为' + location.href, 'color:purple;')
  1416. settings.path = new URLSearchParams(new URL(lastUrl).search).get('path');
  1417. setTimeout(() => {
  1418. $msg.info('重新加载字幕')
  1419. settings.subtitles = null
  1420. document.querySelector('.vp-tabs__header-item:nth-child(4)').click();
  1421. setTimeout(() => {
  1422. document.querySelector('.vp-tabs__header-item:nth-child(1)').click();
  1423. }, 500)
  1424. }, 4000)
  1425.  
  1426. }
  1427. }, 500)
  1428.  
  1429. function showPdf() {
  1430. document.querySelector('.vp-tabs__header').children[2].click()
  1431. const doShow = () => {
  1432. if (!settings.timePoints || settings.timePoints.length == 0) {
  1433. console.log("正在加载课件时间点...");
  1434. setTimeout(doShow, 500);
  1435. } else {
  1436. const video = document.createElement('video');
  1437. const canvas = document.createElement('canvas');
  1438. const context = canvas.getContext('2d');
  1439. let aiCourse = document.querySelector(".vp-ai-course")
  1440. let aiCourseTools = null;
  1441. let logText = null;
  1442. let reloadBtn = null;
  1443. let exportBtn = null;
  1444. let imageContainer = document.getElementById("export-image-container")
  1445. // if (aiCourse && !aiCourse.querySelector('.vp-ai-course-tools')) {
  1446. if (aiCourse) {
  1447. aiCourse.innerHTML = ''
  1448. aiCourseTools = createElement('div', 'vp-ai-course-tools', {style:'margin-bottom:5px;'})
  1449. logText = createElement('span', '', {style:'margin-right:10px;font-size:16px;'})
  1450. reloadBtn = createElement('button', '', {innerText: '重新加载', disabled: true, style: 'margin-right:10px;padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'});
  1451. exportBtn = createElement('button', '', {innerText: '导出', disabled: true, style: 'padding: 3px 10px;font-size: 14px;background:#fff;border:1px solid #ccc;cursor:pointer;'});
  1452. reloadBtn.onclick = () => {
  1453. if (!reloadBtn.disabled) {
  1454. showPdf();
  1455. }
  1456. }
  1457. let exportLock = false;
  1458. exportBtn.onclick = () => {
  1459. if (!exportBtn.disabled && !exportLock) {
  1460. exportLock = true;
  1461. $msg.info("正在写入pdf")
  1462. if (settings.captureHls)
  1463. settings.captureHls.destroy();
  1464. settings.captureHls = null;
  1465. // 导出pdf
  1466. const imgs = imageContainer.querySelectorAll('img')
  1467. let w = imgs[0].naturalWidth, h = imgs[0].naturalHeight
  1468. let pdf = new jspdf.jsPDF('l', 'px', [w, h]);
  1469. for (let i = 0; i < imgs.length; i++) {
  1470. let img = imgs[i]
  1471. pdf.addImage(img.src, 'JPEG', 0, 0, w, h)
  1472. pdf.addPage([img.naturalWidth, img.naturalHeight], 'l')
  1473. }
  1474. const targetPage = pdf.internal.getNumberOfPages()
  1475. pdf.deletePage(targetPage)
  1476. pdf.save(document.querySelector('.vp-personal-home-layout__video > .vp-toolsbar > .vp-toolsbar__title').title + '.pdf')
  1477. $msg.success("导出成功")
  1478. settings.timePoints = []
  1479. exportLock = false;
  1480. }
  1481. }
  1482. aiCourseTools.appendChild(logText)
  1483. aiCourseTools.appendChild(reloadBtn)
  1484. aiCourseTools.appendChild(exportBtn)
  1485. aiCourse.append(aiCourseTools);
  1486. }
  1487. if (!imageContainer) {
  1488. imageContainer = document.createElement('div')
  1489. imageContainer.id = "export-image-container"
  1490. imageContainer.style.overflowY = 'auto'
  1491. } else {
  1492. imageContainer.innerHTML = ''
  1493. }
  1494. aiCourse.appendChild(imageContainer)
  1495.  
  1496. //const hls = new Hls();
  1497. if (settings.captureHls) {
  1498. settings.captureHls.destroy();
  1499. settings.captureHls = null;
  1500. }
  1501. settings.captureHls = new Hls();
  1502. const hls = settings.captureHls;
  1503. video.volume = 0
  1504.  
  1505. hls.on(Hls.Events.MEDIA_ATTACHED, function() {
  1506. hls.loadSource(settings.m3u8url);
  1507. });
  1508.  
  1509. hls.on(Hls.Events.MANIFEST_PARSED, function() {
  1510. video.play();
  1511. });
  1512.  
  1513. hls.on(Hls.Events.ERROR, function (event, data) {
  1514. // console.log("HLS ERROR");
  1515. // console.log(event, data)
  1516. if (data.fatal && data.type == Hls.ErrorTypes.MEDIA_ERROR) {
  1517. $msg.error("导出出现异常,尝试恢复")
  1518. hls.recoverMediaError();
  1519. logText.innerHTML = '<span style="color:red">导出出现异常,尝试恢复</span>'
  1520. } else if (data.fatal) {
  1521. $msg.error("导出失败,请重试")
  1522. logText.innerHTML = '<span style="color:red">导出失败,请重试</span>'
  1523. reloadBtn.disabled = false;
  1524. }
  1525. })
  1526.  
  1527. video.oncanplay = function() {
  1528. video.oncanplay = null;
  1529. canvas.width = video.videoWidth;
  1530. canvas.height = video.videoHeight;
  1531. function captureScreenshot(timePoints, index) {
  1532. if (index >= timePoints.length) {
  1533. // 销毁视频对象
  1534. settings.captureHls.destroy();
  1535. settings.captureHls = null;
  1536. logText.innerHTML = '加载完成';
  1537. reloadBtn.disabled = false;
  1538. exportBtn.disabled = false;
  1539. return;
  1540. }
  1541. logText.innerHTML = `加载中(${index+1}/${timePoints.length})`
  1542. const time = timePoints[index]
  1543. video.pause();
  1544. video.currentTime = time;
  1545. video.onseeked = function() {
  1546. video.onseeked = null;
  1547. context.drawImage(video, 0, 0, canvas.width, canvas.height);
  1548. const imgDataUrl = canvas.toDataURL('image/jpeg', 1);
  1549. const imgWrapper = createElement('div', '', {style:'position:relative;width:100%;height:fit-content;'})
  1550. const img = createElement('img', '', {src: imgDataUrl, style: 'width:100%;'});
  1551. imgWrapper.append(img)
  1552. imageContainer.appendChild(imgWrapper);
  1553. imgWrapper.addEventListener('mouseenter', () => {
  1554. const imgOverlay = createElement('div', 'img-index-text', {textContent:`${index+1}/${timePoints.length}`, style: "font-size:13px;position:absolute;top:0;left:0;background:black;color:white;padding:5px;border-radius:5px;"});
  1555. imgWrapper.appendChild(imgOverlay);
  1556. });
  1557. imgWrapper.addEventListener('mouseleave', () => {
  1558. const imgOverlay = imgWrapper.querySelector('.img-index-text');
  1559. imgWrapper.removeChild(imgOverlay);
  1560. });
  1561. captureScreenshot(timePoints, index+1)
  1562. };
  1563. }
  1564. captureScreenshot(settings.timePoints, 0)
  1565. };
  1566. hls.attachMedia(video);
  1567. }
  1568. }
  1569. if (!unsafeWindow.jspdf) {
  1570. let script = document.createElement('script')
  1571. script.src = 'https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.1.1/jspdf.umd.min.js'
  1572. document.head.appendChild(script)
  1573. script.onload = doShow
  1574. $msg.info("正在引入pdf所需js依赖")
  1575. } else {
  1576. doShow()
  1577. }
  1578. }
  1579.  
  1580. })()