Youtube Full Text

在线字幕阅读/下载神器! - View full text of the subtitles just online! And even download them as srt files !

当前为 2021-02-09 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Full Text
  3. // @namespace https://gf.qytechs.cn/zh-CN/scripts/421483-youtube-full-text
  4. // @version 1
  5. // @description 在线字幕阅读/下载神器! - View full text of the subtitles just online! And even download them as srt files !
  6. // @author KnIfER
  7. // @include https://*youtube.com/*
  8. // ==/UserScript==
  9.  
  10. (function() {
  11. 'use strict';
  12. var loadOnStart = false; /* true false 是否自动分析字幕 */
  13. var pinFTMenu = false; /* true false 是否不自动关闭字幕列表 */
  14. var autoFTM = false; /* true false 是否自动打开字幕列表 */
  15.  
  16. var cssData = "#TextView{position:fixed;bottom:0;left:0;width:100%;height:30px;box-sizing:border-box;background:#fff;z-index: 1000;overflow-y:scroll}#drag_resizer{position:sticky;top:0;right:0;height:6px;width:100%;padding:0;cursor:ns-resize}#ftv{margin-top:9px;margin-left:5px;font-size:x-large;padding:0 100px 0 100px;}a.ft-time:before{content:attr(data-val)}a.ft-time{text-decoration:none;color:blue;user-select:none;-moz-user-select:none}.ft-ln.curr {border-bottom: 2px solid #0000ffac;}ytd-masthead{background: transparent;}";
  17.  
  18. var btnCss = ".ytp-gradient-top,.ytp-chrome-top{opacity:0}.ytp-fulltext-menu{right: 12px;bottom: 53px;z-index: 71;will-change: width,height;}.ytp-fulltext-menu .ytp-menuitem-label{width: 65%;}";
  19.  
  20. // the dialog
  21. var pageData = '<p id="drag_resizer"></p><p id="ftv">CAPTION</p>';
  22. var menuData = `<div class="ytp-popup ytp-fulltext-menu" data-layer="6" id="yft-select"
  23. style="width: 251px; height: 137px; display: block;">
  24. <div class="ytp-panel" style="min-width: 250px; width: 251px; height: 137px;" id="yft_cc">
  25. <div class="ytp-panel-menu" role="menu" style="height: 137px;"></div>
  26. </div>
  27. </div>`;
  28.  
  29. // the svg icon from Android Assets && the VectorPathTweaker plugin
  30. var btnData = '<svg height="100%" version="1.1" viewBox="0 0 36 36" width="100%"><g class="ytp-fullscreen-button-corner-0"><use class="ytp-svg-shadow" xlink:href="#ytp-id-99"></use><path class="ytp-svg-fill" d="M18.97,18h6.82v1.46h-6.82zM18.97,15.57h6.82L25.8,17.03h-6.82zM18.97,20.43h6.82L25.8,21.89h-6.82zM26.77,10.19L9.23,10.19c-1.07,0 -1.96,0.88 -1.96,1.96v12.67c0,1.07 0.88,1.96 1.96,1.96h17.55c1.07,0 1.96,-0.88 1.96,-1.96L28.73,12.15c0,-1.07 -0.88,-1.96 -1.96,-1.96zM26.77,24.83h-8.77L18,12.15h8.77v12.67z" id="ytp-id-99"></path></g></svg>';
  31.  
  32. // api address
  33. var baseUrl = 'https://video.google.com/timedtext';
  34. // the panel, the text, the button
  35. var YFT, ftv, BTN;
  36.  
  37. // the menu
  38. var YFT_menu, YFT_mps;
  39. // video tag
  40. var H5Vid;
  41. var lrcLoaded;
  42.  
  43. function initBTN(){
  44. if(!BTN){
  45. var doc=document,rct = doc.getElementsByClassName("ytp-right-controls")[0];
  46. if(rct&&rct.firstChild){
  47. // insert a control btn
  48. var e = doc.createElement("style");
  49. e.id = "FTCB"
  50. doc.head.appendChild(e);
  51. e.innerHTML = btnCss;
  52. e = doc.createElement("button");
  53. e.id = "YFTB"
  54. e.className = "ytp-fulltext-button ytp-button";
  55. e.title="Full text (t)";
  56. rct.insertBefore(e,rct.firstChild);
  57. e.innerHTML = btnData;
  58. e.onclick = function() {
  59. if(YFT_mps) {
  60. var st = YFT_mps;
  61. if(st.display!="none") {
  62. st.display="none"
  63. } else {
  64. st.display="";
  65. build_cc_menu()
  66. }
  67. } else {
  68. build_cc_menu()
  69. }
  70. }
  71. BTN=e;
  72. if(autoFTM) {
  73. build_cc_menu()
  74. }
  75. if(loadOnStart) {
  76. // todo load initial lyrics
  77. build_cc_menu(1);
  78. initYFT();
  79. }
  80. } else {
  81. setTimeout(initBTN, 100);
  82. }
  83. }
  84. }
  85.  
  86. function initYFT(H){
  87. if(!YFT) {
  88. var doc=document,item = doc.createElement("style");
  89. item.id = "YFT"
  90. doc.head.appendChild(item);
  91. item.innerHTML = cssData;
  92.  
  93. item=doc.createElement("div");
  94. item.id="TextView";
  95. doc.body.appendChild(item);
  96. item.innerHTML=pageData;
  97. YFT = item;
  98. ftv = YFT.children[1];
  99.  
  100. // drag-resize the TextView
  101. //item.onload= ()=> bindResize();
  102. bindResize();
  103.  
  104. function bindResize(){
  105. var tvP = YFT;
  106. var tvPs = tvP.style,
  107. x = 0;
  108. var el = drag_resizer;
  109. function mousedown(e){
  110. if(e.clientY==undefined)
  111. e.clientY=e.originalEvent.changedTouches[0].clientY;
  112. x = e.clientY + tvP.offsetHeight;
  113. e.preventDefault()
  114. document.addEventListener("mousemove", mouseMove); document.addEventListener("mouseup", mouseUp);
  115. };
  116. function mouseMove(e){
  117. if(e.clientY==undefined)
  118. e.clientY=e.originalEvent.changedTouches[0].clientY;
  119. tvPs.height = x - e.clientY + 'px';
  120. }
  121. function mouseUp(){
  122. document.removeEventListener("mousemove", mouseMove); document.removeEventListener("mouseup", mouseUp);
  123. }
  124.  
  125. el.addEventListener("mousedown", mousedown);
  126. el.addEventListener("touchstart", mousedown);
  127. el.addEventListener("touchmove", mouseMove);
  128. el.addEventListener("touchend", mouseUp);
  129. }
  130. installTimers();
  131. //var insertionLis = e => {
  132. // //console.log("DOMNodeInserted")
  133. // if(document.body.lastElementChild!=YFT){
  134. // document.body.removeChild(YFT);
  135. // document.body.appendChild(YFT);
  136. // }
  137. //};
  138. //document.body.addEventListener('DOMNodeInserted', insertionLis)
  139. }
  140. ensureFTH(H||30)
  141. }
  142. function ensureFTH(e){
  143. // ensure visibility
  144. if(YFT){
  145. var h = parseFloat(YFT.style.height);
  146. if(h!=h||h<e) {
  147. YFT.style.height = e+"px"
  148. }
  149. if(YFT.style.display!=="") {
  150. YFT.style.display = ""
  151. }
  152. }
  153. }
  154. /*via mdict-js*/
  155. function reduce(val,arr,st,ed) {
  156. var len = ed-st;
  157. if (len > 1) {
  158. len = len >> 1;
  159. return val > arr[st + len - 1].endTime
  160. ? reduce(val,arr,st+len,ed)
  161. : reduce(val,arr,st,st+len);
  162. } else {
  163. return arr[st];
  164. }
  165. }
  166.  
  167. function installTimers(){
  168. if(H5Vid==null) {
  169. H5Vid=document.querySelector('video')
  170. if(H5Vid==null) {
  171. setTimeout(initTimer, 100)
  172. } else {
  173. H5Vid.addEventListener('timeupdate', e => {
  174. // lyrics scroll sync to time
  175. var tm=H5Vid.currentTime;
  176. if(lrcArr&&(!lcN||tm>=lcN.endTime||tm<lcN.startTime)) {
  177. var n = reduce(tm,lrcArr,0,lrcArr.length);
  178. if(n&&n!=lcN) {
  179. lcN = n;
  180. if(lcE) {
  181. lcE.className="ft-ln";
  182. }
  183. n = n.ele;
  184. lcE = n;
  185. if(n) {
  186. n.className+=" curr";
  187. }
  188. if(window.getSelection().isCollapsed
  189. &&(n.offsetTop+n.offsetHeight>TextView.scrollTop+TextView.offsetHeight
  190. ||n.offsetTop<TextView.scrollTop)) {
  191. TextView.scrollTop=n.offsetTop;
  192. //TextView.scrollTo(n.offsetTop);
  193. }
  194. }
  195. }
  196. })
  197. window.addEventListener("click", function(e){
  198. if(e.srcElement.className==="ft-time") {
  199. e.preventDefault();
  200. H5Vid.currentTime=parseFloat(e.srcElement.getAttribute("data-tm"));
  201. }
  202. });
  203. }
  204. }
  205. }
  206. // http://qtdebug.com/fe-srt/
  207. function parseSrtSubtitles(srt) {
  208. var subtitles = [];
  209. var textSubtitles = srt.split('\n\n'); // 每条字幕的信息,包含了序号,时间,字幕内容
  210. for (var i = 0; i < textSubtitles.length; ++i) {
  211. var textSubtitle = textSubtitles[i].split('\n');
  212. if (textSubtitle.length >= 2) {
  213. var sn = textSubtitle[0];
  214. var tms = textSubtitle[1].split(' --> ');
  215. var startTime = toSeconds(tms[0]);
  216. var endTime = toSeconds(tms[1]);
  217. var content = textSubtitle[2];
  218. // 字幕可能有多行
  219. if (textSubtitle.length > 2) {
  220. for (var j = 3; j < textSubtitle.length; j++) {
  221. content += ' ' + textSubtitle[j];
  222. }
  223. }
  224. // 你没有对象
  225. var subtitle = {
  226. sn: sn,
  227. startTime: startTime,
  228. endTime: endTime,
  229. content: content
  230. };
  231. subtitles.push(subtitle);
  232. }
  233. }
  234. return subtitles;
  235. }
  236. function toSeconds(t) {
  237. var s = 0.0;
  238. if (t) {
  239. var p = t.trim().split(':');
  240. for (var i = 0; i < p.length; i++) {
  241. s = s * 60 + parseFloat(p[i].replace(',', '.'));
  242. }
  243. }
  244. return s;
  245. }
  246. var lrcArr;
  247. var lcN, lcE;
  248. // Append full text.
  249. function APFT(e, d) {
  250. //console.log("APFT", e, d);
  251. var lrc = e.srt;
  252. if(d) {
  253. var t=document.getElementsByTagName("H1")[0];
  254. if(t)t=t.innerText;
  255. else t=document.title;
  256. downloadString(lrc, "text/plain", t+"."+(e.lang_code||"a")+".srt");
  257. return;
  258. }
  259. unsafeWindow.srtlrc=e;
  260. // parse
  261. var lrcs = parseSrtSubtitles(lrc);
  262. var span="";
  263. var lastTime=0;
  264. // concatenate
  265. for(var i=0;i<lrcs.length;i++){
  266. var lI=lrcs[i];
  267. var text = lI.content;
  268. var lnSep="<br><br>";
  269. var sepLn="";
  270. if(lI.startTime-lastTime>3){
  271. var idx = text.indexOf(".");
  272. // skip numberic dots
  273. while(idx>0) {
  274. if(idx+1>=text.length||text[idx+1]<=' ') {
  275. break;
  276. }
  277. idx = text.indexOf(".", idx+1);
  278. }
  279. if(idx<0) idx = text.indexOf("。");
  280. if(idx<0) idx = text.indexOf(",");
  281. if(idx<0) idx = text.indexOf(",");
  282. if(idx>=0) {
  283. text=" "+text.substring(0, idx+1)
  284. +lnSep+text.substring(idx+1);
  285. } else {
  286. sepLn = lnSep;
  287. }
  288. lnSep = " ";
  289. } else {
  290. // merge to previous line
  291. text="&nbsp;"+text;
  292. lnSep = "";
  293. }
  294. //console.log(lI.startTime-lastTime);
  295. var s = lI.startTime;
  296. var m = parseInt(lI.startTime/60);
  297. span+=sepLn+"<a class='ft-time' href='' data-val='" + " "
  298. +(m+":"+parseInt(s-m*60))+lnSep+"' data-tm='"+s+"'></a>"
  299. +"<span class='ft-ln'>"+text+"</span>"
  300. lastTime = lI.startTime;
  301. }
  302. ftv.innerHTML=span;
  303. // attach ele to array
  304. lrcArr = lrcs;
  305. lcN = 0;
  306. var cc=0;
  307. var sz = ftv.childElementCount;
  308. for(var i=0;i<sz,cc<lrcArr.length;i++) {
  309. if(ftv.children[i].className==="ft-ln") {
  310. lrcArr[cc++].ele=ftv.children[i];
  311. }
  312. }
  313. window.lrcArr=lrcArr;
  314. //console.log(lrcArr);
  315. }
  316. //window.APFT = APFT;
  317. initBTN();
  318. unsafeWindow.yft_captions = []; // store all subtitle
  319.  
  320. // trigger when loading new page
  321. // (actually this would also trigger when first loading, that's not what we want, that's why we need to use firsr_load === false)
  322. // (new Material design version would trigger this "yt-navigate-finish" event. old version would not.)
  323. var body = document.getElementsByTagName("body")[0];
  324. body.addEventListener("yt-navigate-finish", function (event) {
  325. if (is_video_page()&&autoFTM) {
  326. if(build_cc_menu()) {
  327. var st = YFT_mps;
  328. if(st.display!="") {
  329. st.display=""
  330. }
  331. }
  332. }
  333. });
  334.  
  335. // trigger when loading new page
  336. // (old version would trigger "spfdone" event. new Material design version not sure yet.)
  337. window.addEventListener("spfdone", function (e) {
  338. //if (is_video_page()) {
  339. // remove_dwnld_btn();
  340. // var checkExist = setInterval(function () {
  341. // if (unsafeWindow.watch7_headline) {
  342. // init();
  343. // clearInterval(checkExist);
  344. // }
  345. // }, 330);
  346. //}
  347. });
  348. function is_video_page() {
  349. return get_vid() !== null;
  350. }
  351.  
  352. function get_vid() {
  353. return getURLParameter('v');
  354. }
  355.  
  356. //https://stackoverflow.com/questions/11582512/how-to-get-url-parameters-with-javascript/11582513#11582513
  357. function getURLParameter(name) {
  358. return decodeURIComponent((new RegExp('[?|&]' + name + '=' + '([^&;]+?)(&|#|;|$)').exec(location.search) || [null, ''])[1].replace(/\+/g, '%20')) || null;
  359. }
  360. // https://stackoverflow.com/questions/32142656/get-youtube-captions#58435817
  361. function buildXmlurl(videoId, loc) {
  362. return `${baseUrl}?lang=${loc}&v=${videoId}`;//&fmt=json3
  363. }
  364. // pull the selected caption.
  365. function pullLyrics(e, d) {
  366. var url;
  367. if(e==0) {
  368. console.log("auto");
  369. url = get_auto_xml_url();
  370. console.log("auto", url);
  371. }
  372. e = yft_captions[e]
  373. if(e) {
  374. if(!e.srt)
  375. fetch(url||buildXmlurl(get_vid(), e.lang_code))
  376. .then(v => v.text())
  377. .then(v => (new window.DOMParser()).parseFromString(v, "text/xml"))
  378. .then(v => {
  379. v = buildSrtFromXML(v);
  380. e.srt = v;
  381. APFT(e, d)
  382. })
  383. else APFT(e, d)
  384. }
  385. }
  386. function buildMenu(e){
  387. return `<div class="ytp-menuitem" aria-haspopup="true" role="menuitem" tabindex="${e.cid||0}">
  388. <div class="ytp-menuitem-icon"></div>
  389. <div class="ytp-menuitem-label">
  390. ${e.lang_name}
  391. </div>
  392. <div class="ytp-menuitem-content">
  393. 下载
  394. </div>
  395. </div>`;
  396. }
  397. function menuic(e){
  398. var t = e.target;
  399. var i = parseInt(t.parentNode.getAttribute("tabindex"));
  400. if(i==i) {
  401. if(t.className==="ytp-menuitem-content") {
  402. // 下载
  403. pullLyrics(i, 1);
  404. } else {
  405. // 查看
  406. initYFT(120);
  407. pullLyrics(i);
  408. }
  409. }
  410. t.blur();
  411. if(!pinFTMenu) {
  412. YFT_mps.display="none";
  413. }
  414. }
  415. var lastVid;
  416. function build_cc_menu(src) {
  417. var vid = get_vid();
  418. if(vid==lastVid) {
  419. return false;
  420. }
  421. lastVid=vid;
  422. if(loadOnStart) {
  423. src=1;
  424. }
  425. // todo validify auto caption exists
  426. if(!YFT_menu&&unsafeWindow.movie_player) {
  427. var item = document.createElement("div");
  428. movie_player.appendChild(item);
  429. item.innerHTML = menuData;
  430. YFT_mps = item.style;
  431. YFT_menu = unsafeWindow.yft_cc;
  432. if(src==1&&!autoFTM) {
  433. YFT_mps.display = "none";
  434. }
  435. }
  436. if(YFT_menu) {
  437. YFT_menu.innerHTML = "";
  438. var list_url = `${baseUrl}?hl=en&v=${vid}&type=list`;
  439. console.log("loading_list::", list_url);
  440. makeRequest('GET',list_url, function (xhr) {
  441. // todo auto select if requested
  442. try{
  443. yft_captions = [];
  444. var tracks = xhr.responseXML.getElementsByTagName('track');
  445. xhr="";
  446. var autosel=-1;
  447. for (var i = 0, len = tracks.length, xml, ety; i <= len; i++) {
  448. if(i==0) {
  449. ety={lang_code:'AUTO',lang_name:'AUTO'}
  450. } else {
  451. xml = tracks[i-1];
  452. ety = {
  453. lang_code: xml.getAttribute('lang_code'),
  454. lang_name: xml.getAttribute('lang_original')
  455. ||xml.getAttribute('lang_translated'),
  456. cid:i
  457. }
  458. if(src==1&&xml.getAttribute('lang_default')) {
  459. autosel=i;
  460. src=0;
  461. }
  462. }
  463. yft_captions.push(ety); // 加到 yft_captions, 待会靠它下载
  464. xhr+=buildMenu(ety);
  465. }
  466. if(src==1) {
  467. autosel=0;
  468. }
  469. console.log("autosel", autosel);
  470. YFT_menu.innerHTML=xhr;
  471. var cc = YFT_menu.children;
  472. for (var i = 0, len = cc.length; i < len; i++) {
  473. cc[i].onclick = menuic;
  474. if(autosel==i) {
  475. initYFT(120);
  476. pullLyrics(i);
  477. }
  478. }
  479. } catch(e) {console.log(e)}
  480. });
  481. } else {
  482. lastVid="";
  483. }
  484. return true;
  485. }
  486.  
  487. // 处理时间. 比如 start="671.33" start="37.64" start="12" start="23.029"
  488. // 处理成 srt 时间, 比如 00:00:00,090 00:00:08,460 00:10:29,350
  489. function process_time(s) {
  490. s = s.toFixed(3);
  491. // 超棒的函数, 不论是整数还是小数都给弄成3位小数形式
  492. // 举个柚子:
  493. // 671.33 -> 671.330
  494. // 671 -> 671.000
  495. // 注意函数会四舍五入. 具体读文档
  496.  
  497. var array = s.split('.');
  498. // 把开始时间根据句号分割
  499. // 671.330 会分割成数组: [671, 330]
  500.  
  501. var Hour = 0;
  502. var Minute = 0;
  503. var Second = array[0]; // 671
  504. var MilliSecond = array[1]; // 330
  505. // 先声明下变量, 待会把这几个拼好就行了
  506.  
  507. // 我们来处理秒数. 把"分钟"和"小时"除出来
  508. if (Second >= 60) {
  509. Minute = Math.floor(Second / 60);
  510. Second = Second - Minute * 60;
  511. // 把 秒 拆成 分钟和秒, 比如121秒, 拆成2分钟1秒
  512.  
  513. Hour = Math.floor(Minute / 60);
  514. Minute = Minute - Hour * 60;
  515. // 把 分钟 拆成 小时和分钟, 比如700分钟, 拆成11小时40分钟
  516. }
  517. // 分钟,如果位数不够两位就变成两位,下面两个if语句的作用也是一样。
  518. if (Minute < 10) {
  519. Minute = '0' + Minute;
  520. }
  521. // 小时
  522. if (Hour < 10) {
  523. Hour = '0' + Hour;
  524. }
  525. // 秒
  526. if (Second < 10) {
  527. Second = '0' + Second;
  528. }
  529. return Hour + ':' + Minute + ':' + Second + ',' + MilliSecond;
  530. }
  531.  
  532. // copy from: https://gist.github.com/danallison/3ec9d5314788b337b682
  533. // Thanks! https://github.com/danallison
  534. // work in Chrome 66
  535. // test passed: 2018-5-19
  536. function downloadString(text, fileType, fileName) {
  537. var blob = new Blob([text], {type: fileType});
  538. var a = document.createElement('a');
  539. a.download = fileName;
  540. a.href = URL.createObjectURL(blob);
  541. a.dataset.downloadurl = [fileType, a.download, a.href].join(':');
  542. a.style.display = "none";
  543. document.body.appendChild(a);
  544. a.click();
  545. document.body.removeChild(a);
  546. setTimeout(function () {
  547. URL.revokeObjectURL(a.href);
  548. }, 1500);
  549. }
  550.  
  551. // https://css-tricks.com/snippets/javascript/unescape-html-in-js/
  552. // turn HTML entity back to text, example: &quot; should be "
  553. function htmlDecode(input) {
  554. var e = document.createElement('div');
  555. e.class = 'dummy-element-for-tampermonkey-Youtube-Subtitle-Downloader-script-to-decode-html-entity';
  556. e.innerHTML = input;
  557. return e.childNodes.length === 0 ? "" : e.childNodes[0].nodeValue;
  558. }
  559.  
  560. // return URL or null;
  561. // later we can send a AJAX and get XML subtitle
  562. function get_auto_xml_url() {
  563. try {
  564. var captionTracks = get_captionTracks()
  565. for (var index in captionTracks) {
  566. var caption = captionTracks[index];
  567. if (caption.kind === 'asr') {
  568. return captionTracks[index].baseUrl;
  569. }
  570. // ASR – A caption track generated using automatic speech recognition.
  571. // https://developers.google.com/youtube/v3/docs/captions
  572. }
  573. return false;
  574. } catch (e) {
  575. console.log(e);
  576. return false;
  577. }
  578. }
  579. async function get_auto_subtitle() {
  580. var url = get_auto_xml_url();
  581. console.log("dwnld_auto_url::", url);
  582. if (url == false) {
  583. return false;
  584. }
  585. var result = await getUrl(url)
  586. return result
  587. }
  588. // Youtube return XML. we want SRT
  589. // input: Youtube XML format
  590. // output: SRT format
  591. function buildSrtFromXML(youtube_xml_string) {
  592. if (youtube_xml_string === '') {
  593. return false;
  594. }
  595. var text = youtube_xml_string.getElementsByTagName('text');
  596. var result = '';
  597. var BOM = '\uFEFF';
  598. result = BOM + result; // store final SRT result
  599. var len = text.length;
  600. for (var i = 0; i < len; i++) {
  601. var index = i + 1;
  602. var content = text[i].textContent.toString();
  603. content = content.replace(/(<([^>]+)>)/ig, ""); // remove all html tag.
  604. var start = text[i].getAttribute('start');
  605. var end = parseFloat(text[i].getAttribute('start')) + parseFloat(text[i].getAttribute('dur'));
  606. var new_line = "\n";
  607. result = result + index + new_line;
  608. // 1
  609. if (i + 1 >= len) {
  610. end = parseFloat(text[i].getAttribute('start')) + parseFloat(text[i].getAttribute('dur'));
  611. } else {
  612. end = text[i + 1].getAttribute('start');
  613. }
  614.  
  615. var start_time = process_time(parseFloat(start));
  616. var end_time = process_time(parseFloat(end));
  617. result = result + start_time;
  618. result = result + ' --> ';
  619. result = result + end_time + new_line;
  620. // 00:00:01,939 --> 00:00:04,350
  621.  
  622. content = htmlDecode(content);
  623. // turn HTML entity back to text. example: &#39; back to apostrophe (')
  624.  
  625. result = result + content + new_line + new_line;
  626. // everybody Craig Adams here I'm a
  627. }
  628. return result;
  629. }
  630. function get_captionTracks() {
  631. var json = null
  632. if (unsafeWindow.youtube_playerResponse_1c7) {
  633. json = youtube_playerResponse_1c7;
  634. } else if(ytplayer.config.args.player_response) {
  635. let raw_string = ytplayer.config.args.player_response;
  636. json = JSON.parse(raw_string);
  637. } else if (ytplayer.config.args.raw_player_response) {
  638. json = ytplayer.config.args.raw_player_response;
  639. }
  640. let captionTracks = json.captions.playerCaptionsTracklistRenderer.captionTracks;
  641. return captionTracks
  642. }
  643. // https://stackoverflow.com/questions/48969495/in-javascript-how-do-i-should-i-use-async-await-with-xmlhttprequest
  644. function makeRequest(method, url, load) {
  645. return new Promise(function (resolve, reject) {
  646. let xhr = new XMLHttpRequest();
  647. xhr.open(method, url);
  648. xhr.timeout = 2000;
  649. xhr.onload = function () {
  650. if (this.status >= 200 && this.status < 300) {
  651. if(load) {
  652. load(xhr);
  653. resolve('');
  654. } else {
  655. resolve(xhr.responseXML);
  656. }
  657. } else {
  658. reject({
  659. status: this.status,
  660. statusText: xhr.statusText
  661. });
  662. }
  663. };
  664. xhr.onerror = function () {
  665. reject({
  666. status: this.status,
  667. statusText: xhr.statusText
  668. });
  669. };
  670. xhr.send();
  671. });
  672. }
  673. async function getUrl(url) {
  674. return makeRequest("GET", url);
  675. }
  676. })();

QingJ © 2025

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