bilibili-video remain time on tab title

在分页标题中 显示bilibili影片剩馀时间

  1. // ==UserScript==
  2. // @name bilibili-video remain time on tab title
  3. // @namespace smallsupo
  4. // @version 1.0
  5. // @description show bilibili video remaining time on tab title
  6. // @description:zh-CN 在分页标题中 显示bilibili影片剩馀时间
  7. // @description:zh-TW 在分頁標題中 顯示bilibili影片剩餘時間
  8. // @author smallsupo (smallsupo@gmail.com)
  9. // @match *://www.bilibili.com/video/*
  10. // @icon https://t2.gstatic.com/faviconV2?client=SOCIAL&type=FAVICON&fallback_opts=TYPE,SIZE,URL&url=http://bilibili.com&size=32
  11. // @grant none
  12. // @license Copyright smallsupo
  13. // ==/UserScript==
  14.  
  15. //--------------------------------------------
  16. let showWhenFullScreenVideo=true;let fontsize="36px";
  17. class STD{ //smallsupo tools - dom ui
  18. static DISPLAY_NONE=0;static VISIBILITY_HIDDEN=1;
  19. constructor(){}
  20. static createEL(htmltag,id,style){
  21. let e=document.createElement(htmltag);if(id!=null)e.setAttribute("id",id);if(style!=null)e.setAttribute("style",style);
  22. return e;
  23. }
  24. static eventStopBubbling(e) {
  25. e = window.event || e;if (e.stopPropagation) {e.stopPropagation();} else {e.cancelBubble = true;}
  26. }
  27. static getDomNode(root,queryArray){
  28. let node=root;
  29. if(queryArray.length>0){
  30. node=root.querySelector(queryArray[0]);
  31. for(let i=1;i<queryArray.length;i++){if(node!=null){node=node.querySelector(queryArray[i]);}}
  32. }
  33. return node;
  34. }
  35. static getDomNodes(root,queryArray){
  36. let nodes=null;
  37. let endquery=queryArray.pop();
  38. let node=STD.getDomNode(root,queryArray);
  39. if(node!=null){nodes=node.querySelectorAll(endquery);}
  40. return nodes;
  41. }
  42. static getDomAttribute(root,queryArray,attribute){
  43. let value=null;let node=this.getDomNode(root,queryArray);
  44. if(node!=null){if(node.hasAttribute(attribute))value=node.getAttribute(attribute);}
  45. return value;
  46. }
  47. static getDomInnerText(root,queryArray){
  48. let value=null;let node=this.getDomNode(root,queryArray);
  49. if(node!=null){value=node.innerText;}
  50. return value;
  51. }
  52. }
  53. let titleTime;
  54. let title;
  55. let totaltime;let currenttime;
  56. let observer=null;
  57. let timeInterval=null;let timeIntervalRunning=false;
  58. let delaytimer=null;
  59. let currentTime;
  60. let remaintime;
  61. let currenturl;
  62. let reget=false;
  63. let urlchanging=false;
  64. const debug=false;
  65. function setTitle(time){
  66. document.title=time+" "+title;
  67. }
  68. function getVideoTotalTime(){
  69. return STD.getDomInnerText(document.getElementsByTagName('body')[0],['span[class="bpx-player-ctrl-time-duration"]']);
  70. }
  71. function getVideoCurrentTime(){
  72. return STD.getDomInnerText(document.getElementsByTagName('body')[0],['span[class="bpx-player-ctrl-time-current"]']);
  73. }
  74. function isPlay(){
  75. let result=false;
  76. let n=STD.getDomNode(document.getElementsByTagName('body')[0],['div[class~="bpx-state-paused"]']);
  77. if(n==null)result=true;
  78. return result;
  79. }
  80. function isHide(){
  81. let result=false;
  82. let n=STD.getDomNode(document.getElementsByTagName('body')[0],['div[class~="bpx-state-no-cursor"]']);
  83. if(n!=null) result=true;
  84. return result;
  85. }
  86. function delay(time) {
  87. return new Promise(resolve => setTimeout(resolve, time));
  88. }
  89. function runRemainTimer(){
  90. if(debug)console.log("play:",isPlay(),"hide:",isHide());
  91. if(isPlay()&&timeIntervalRunning==false){
  92. //clearTimeout(delaytimer);
  93. delaytimer=setTimeout(startTimeInterval,1000);
  94. }else if(!isPlay()){
  95. reget=true;
  96. setTimeout(stopTimeInterval,1000);
  97. }
  98. }
  99. function computeRemainTime(){
  100. currenttime=getVideoCurrentTime();
  101. if(debug)console.log("1 ",currenttime);
  102. currenttime=toTimeType(currenttime);
  103. if(debug)console.log("2 ",currenttime);
  104. totaltime=getVideoTotalTime(); totaltime=toTimeType(totaltime);
  105. remaintime=totaltime-currenttime;
  106. if(debug)console.log("3 ",remaintime);
  107. return parseInt(remaintime);
  108. }
  109. function formatTimeAddZero(s){
  110. let count = (s.match(/:/g) || []).length;
  111. if(count==0)return "0:0:"+s;
  112. if(count==1)return "0:"+s;
  113. return s;
  114. }
  115. function formatRemainTime(time){
  116. let result="";let totalSeconds=time;
  117. let h = Math.floor(totalSeconds / (3600*1000));
  118. totalSeconds %= (3600*1000);
  119. let m = Math.floor(totalSeconds / (60*1000));
  120. let s = Math.round((totalSeconds % (60*1000)) /1000);
  121. if(debug)console.log("4-1 ",h+" "+m+" "+s);
  122. if(h!=0)result+=h+":";
  123. if(m!=0){
  124. if(h!=0){
  125. if(m<10)result+="0"+m+":";else result+=m+":";
  126. }else{
  127. result+=m+":";
  128. }
  129. }else if(m==0){
  130. if(h!=0){result+="00:";}
  131. }
  132. if(s!=0){
  133. if(s<10)result+="0"+s;else result+=s;
  134. }else {result+="00";}
  135. if(h==0&&m==0)result="0:"+result;
  136. if(debug)console.log("4-2 ",result);
  137. return result;
  138. }
  139. function toTimeType(s){
  140. const datefake="2024-01-01 ";
  141. let time=formatTimeAddZero(s)
  142. let d=new Date(datefake+time);
  143. let d1=new Date(datefake);
  144. return parseInt(d.getTime()-d1.getTime());
  145. }
  146. function isFullScreenVideo(){
  147. let result=false;
  148. let n=STD.getDomNode(document.getElementsByTagName('body')[0],['div[class~="bpx-player-container"]']);
  149. if(n!=null) {
  150. if(n.getAttribute("data-screen")=="full")result=true;
  151. }
  152. return result;
  153. }
  154. function isFullWebVideo(){
  155. let result=false;
  156. let n=STD.getDomNode(document.getElementsByTagName('body')[0],['div[class~="bpx-player-container"]']);
  157. if(n!=null) {
  158. if(n.getAttribute("data-screen")=="web")result=true;
  159. }
  160. return result;
  161. }
  162. function setRemainHTMLTagOnFullVideo(text,show){
  163.  
  164. let id="smallsupo_remaintime_fullvideo";
  165. let n=document.getElementById(id);
  166. if(n==null){
  167. let node=document.querySelector('div[class^="bpx-player-video-area"]');
  168. node=node.parentElement;
  169. let x=document.createElement("div");x.setAttribute("id",id);
  170. x.setAttribute("style","position:absolute;z-index:999999;background-color:black;padding:4px;color:white;font-size:"+fontsize+";");
  171. node.appendChild(x);
  172. }else{
  173. if(show){
  174. n.style.display="block";
  175. n.innerText=text;
  176. }else{
  177. n.style.display="none";
  178. }
  179. }
  180. }
  181. function showRemainTimeInTitle(){
  182. if(urlchanging)return;
  183. if(!isPlay()){
  184. reget=true;
  185. setTimeout(stopTimeInterval,1000);
  186. }
  187. if(debug)console.log("showRemainTimeInTitle ",isLive());
  188. if(isLive()){ // living video
  189. /*
  190. if(isHide()){
  191. if(remaintime===undefined){remaintime=getVideoCurrentTime();remaintime=toTimeType(remaintime);reget=false;}
  192. remaintime+=1000*getPlayRate();
  193. setTitle(formatRemainTime(remaintime));
  194. }else{ //show
  195. remaintime=getVideoCurrentTime();
  196. console.log(remaintime);
  197. remaintime=toTimeType(remaintime);
  198. setTitle(formatRemainTime(remaintime));
  199. }
  200. */
  201. }else{ //general video
  202. remaintime=computeRemainTime();
  203. if(remaintime<=0){setTitle("");setRemainHTMLTag("");return;}
  204. let r=formatRemainTime(remaintime/getPlayRate());
  205. setTitle(r);
  206. if(getPlayRate()==1){
  207. setRemainHTMLTag(" (−"+r+")");
  208. if(showWhenFullScreenVideo)setRemainHTMLTagOnFullVideo(r,isFullScreenVideo()||isFullWebVideo());
  209. }else{
  210. setRemainHTMLTag(" (−"+r+" "+getPlayRate()+"x speed)");
  211. if(showWhenFullScreenVideo)setRemainHTMLTagOnFullVideo(r+" ("+getPlayRate()+"x speed)",isFullScreenVideo()||isFullWebVideo());
  212. }
  213. }//end general video
  214. //console.log(": ",currenttime," ",totaltime);
  215. }
  216. function isLive(){
  217. let result=false;
  218. /*
  219. let node=STD.getDomNode(document.getElementsByTagName('body')[0],['span[class="ytp-time-duration"]']);
  220. node=node.parentElement.parentElement;
  221. let text=node.getAttribute("class");
  222. if(text.indexOf("ytp-live")!=-1){
  223. result=true;
  224. }*/
  225. return result;
  226. }
  227. function getPlayRate(){
  228. let rate=1;
  229. if(document.querySelector("video")!=null){rate=document.querySelector("video").playbackRate;}
  230. return rate;
  231. }
  232. function stopTimeInterval(){
  233. if(timeInterval!=null){
  234. if(debug)console.log("stopTimeInterval");
  235. timeIntervalRunning=false;
  236. clearInterval(timeInterval);timeInterval=null;
  237. }
  238. }
  239. function startTimeInterval(){
  240. if(timeInterval==null){
  241. if(debug)console.log("startTimeInterval");
  242. timeIntervalRunning=true;
  243. timeInterval=setInterval(showRemainTimeInTitle,1000);
  244. }
  245. }
  246. function setRemainHTMLTag(text){
  247. let id="smallsupo_remaintime";
  248. let n=document.getElementById(id);
  249. if(n==null){
  250. let node=STD.getDomNode(document.getElementsByTagName('body')[0],['div[class="bpx-player-ctrl-time-label"]']);
  251. if(node!=null){
  252. let x=STD.createEL("span",id,null);
  253. node.appendChild(x);
  254. }
  255. }else{
  256. n.innerText=text;
  257. }
  258. }
  259. function init(){
  260. urlchanging=false;
  261. if(debug)console.log("init");
  262. if(/www.bilibili.com\/video\//.test(window.location.href)){
  263. title=document.title;
  264. totaltime=getVideoTotalTime();
  265. console.log(title+":"+totaltime);
  266. if(totaltime!=null)totaltime=toTimeType(totaltime);
  267. remaintime=undefined;
  268. setRemainHTMLTag("");
  269. startObserver();
  270. }
  271. }
  272. async function uninit(){
  273. if(debug)console.log("uninit");
  274. stopObserver();
  275. await delay(1000);
  276. stopTimeInterval();
  277. }
  278. function stopObserver(){
  279. if(observer!=null){
  280. if(debug)console.log("stopObserver");
  281. observer.disconnect();observer=null;
  282. }
  283. }
  284. function startObserver(){
  285. if(debug)console.log("startObserver");
  286. observer=new MutationObserver(runRemainTimer);
  287. let node=STD.getDomNode(document.getElementsByTagName('body')[0],['span[class="bpx-player-ctrl-time-current"]']);
  288. if(node==null){node=document.getElementsByTagName('body')[0];}
  289. observer.observe(node,{attributes:true,childList:true,subtree:true});
  290. }
  291. function start_page_interval(){
  292. let timer;
  293. //console.log("start_page_interval");
  294. setInterval(()=>{
  295. if (window.location.href !== currenturl) {
  296. urlchanging=true;
  297. currenturl=window.location.href;
  298. //console.log("url changed");
  299. uninit();
  300. clearTimeout(timer);
  301. timer=setTimeout(function() {
  302. init();
  303. }, 3000);
  304. }
  305. }, 1000);
  306. }
  307. setTimeout(function() {
  308. (function() {
  309. console.log("BiliBili-video remain time on tab title...啟動")
  310. start_page_interval();
  311. })();
  312. }, 3000);

QingJ © 2025

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