您需要先安装一个扩展,例如 篡改猴、Greasemonkey 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 暴力猴,之后才能安装此脚本。
您需要先安装一个扩展,例如 篡改猴 或 Userscripts ,之后才能安装此脚本。
您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey,才能安装此脚本。
您需要先安装用户脚本管理器扩展后才能安装此脚本。
show youtube video remaining time on tab title
// ==UserScript== // @name Youtube-video remaining time on tab title // @namespace smallsupo // @version 2.0 // @description show youtube video remaining time on tab title // @description:zh-TW 在分頁標題中 顯示youtube影片剩餘時間 // @author smallsupo ([email protected]) // @match *://www.youtube.com/* // @icon https://www.google.com/s2/favicons?sz=64&domain=youtube.com // @grant none // @noframes // @license Copyright smallsupo // ==/UserScript== //----chage false if you don't what see it -------------- let showTimeOnTabTitle=true; let showTimeWhenFullScreenVideo=true;let fontsize="36px"; //-------------------------------------------- class SmallTools{ constructor(){} attribute(root,querystring,attribute){ let result=null;let n=root;if(n==null)n=document; n=n.querySelector(querystring); if(n!=null)result=n.getAttribute(attribute); return result; } innerText(root,querystring){ let result=null;let n=root;if(n==null)n=document; n=n.querySelector(querystring);if(n!=null)result=n.innerText;return result; } create(htmltag,id,style){ let node=document.createElement(htmltag);if(id!=null)node.setAttribute("id",id);if(style!=null)node.setAttribute("style",style); return node; } } let q=new SmallTools(); let titleTime; let title; let totaltime;let currenttime; let observer=null; let timeInterval=null;let timeIntervalRunning=false; let delaytimer=null; let currentTime; let remaintime; let currenturl; let reget=false; let urlchanging=false; function setTitle(time){ document.title=time+" "+title; } function getVideoTotalTime(){ return q.innerText(document,'span.ytp-time-duration'); } function getVideoCurrentTime(){ return q.innerText(document,'span.ytp-time-current'); } function isPlay(){ let result=false; let n=q.attribute(document,'div#movie_player',"class"); if(n!=null)if(n.indexOf("playing-mode")!=-1)result=true; return result; } function isHide(){ let result=false; let n=q.attribute(document,'div#movie_player',"class"); if(n!=null)if(n.indexOf("ytp-autohide")!=-1)result=true; return result; } function delay(time) { return new Promise(resolve => setTimeout(resolve, time)); } function runRemainTimer(){ //console.log("play:",isPlay(),"hide:",isHide()); if(isPlay()&&timeIntervalRunning==false){ //clearTimeout(delaytimer); delaytimer=setTimeout(startTimeInterval,1000); }else if(!isPlay()){ reget=true; setTimeout(stopTimeInterval,1000); } } function computeRemainTime(){ currenttime=getVideoCurrentTime();if(debug)console.log("1 ",currenttime); currenttime=HmsToMiliseconds(currenttime);if(debug)console.log("2 ",currenttime); totaltime=getVideoTotalTime(); totaltime=HmsToMiliseconds(totaltime); remaintime=totaltime-currenttime;if(debug)console.log("3 ",remaintime); return parseInt(remaintime); } function formatTimeAddZero(s){ let count = (s.match(/:/g) || []).length; if(count==0)return "0:0:"+s; if(count==1)return "0:"+s; return s; } function milisecondsToHms(time){ let result="";let totalSeconds=time; let h = Math.floor(totalSeconds / (3600*1000)); totalSeconds %= (3600*1000); let m = Math.floor(totalSeconds / (60*1000)); let s = Math.round((totalSeconds % (60*1000)) /1000); if(debug)console.log("4-1 ",h+" "+m+" "+s); if(h!=0)result+=h+":"; if(m!=0){ if(h!=0){ if(m<10)result+="0"+m+":";else result+=m+":"; }else{ result+=m+":"; } }else if(m==0){ if(h!=0){result+="00:";} } if(s!=0){ if(s<10)result+="0"+s;else result+=s; }else {result+="00";} if(h==0&&m==0)result="0:"+result; if(debug)console.log("4-2 ",result); return result; } function HmsToMiliseconds(s){ const datefake="2024-01-01 "; let time=formatTimeAddZero(s) //console.log("time ",time); let d=new Date(datefake+time); let d1=new Date(datefake); return parseInt(d.getTime()-d1.getTime()); } function showtabandfullvideo(text){ if(showTimeOnTabTitle)setTitle(text); if(showTimeWhenFullScreenVideo)setRemainHTMLTagOnFullVideo(text,isFullScreenVideo()||isFullWebVideo()); } function showRemainTimeInTitle(){ if(urlchanging)return; if(isLive()){ // living video if(isHide()){ if(remaintime===undefined){remaintime=getVideoCurrentTime();remaintime=HmsToMiliseconds(remaintime);reget=false;} remaintime+=1000*getPlayRate(); showtabandfullvideo(milisecondsToHms(remaintime)); }else{ //show remaintime=getVideoCurrentTime(); //console.log(remaintime); remaintime=HmsToMiliseconds(remaintime); showtabandfullvideo(milisecondsToHms(remaintime)); } }else{ //general video if(isHide()){ if(remaintime===undefined){ remaintime=computeRemainTime(); reget=false; } remaintime-=1000*getPlayRate(); if(remaintime<=0){if(showTimeOnTabTitle)setTitle("");return;} showtabandfullvideo(milisecondsToHms(remaintime/getPlayRate())); }else{ //show remaintime=computeRemainTime(); if(remaintime<=0){if(showTimeOnTabTitle)setTitle("");setRemainHTMLTag("");return;} let r=milisecondsToHms(remaintime/getPlayRate()); showtabandfullvideo(r); if(getPlayRate()==1){setRemainHTMLTag(" (−"+r+")");}else{setRemainHTMLTag(" (−"+r+" "+getPlayRate()+"x speed)");} } }//end general video //console.log(": ",currenttime," ",totaltime); } function isLive(){ let result=false; let node=document.querySelector('span.ytp-time-duration'); node=node.parentElement.parentElement; let text=node.getAttribute("class"); if(text.indexOf("ytp-live")!=-1){ result=true; } return result; } function isFullScreenVideo(){ let result=false; let v=document.querySelector('#movie_player'); if(v!=null){result=v.isFullscreen();} return result; } function isFullWebVideo(){ let result=false; const player = document.getElementById('movie_player'); if (player) { const playerHeight = player.getBoundingClientRect().height; const windowHeight = window.innerHeight; const isFullHeight = Math.abs(playerHeight - windowHeight) < 1; // 容許誤差 1px if(isFullHeight&&!isFullScreenVideo()){ result=true; } } return result; } function getPlayRate(){ let rate=1; let v=document.querySelector('#movie_player'); if(v!=null){rate=v.getPlaybackRate();} return rate; } function stopTimeInterval(){ if(timeInterval!=null){ if(debug)console.log("stopTimeInterval"); timeIntervalRunning=false; clearInterval(timeInterval);timeInterval=null; } } function startTimeInterval(){ if(timeInterval==null){ if(debug)console.log("startTimeInterval"); timeIntervalRunning=true; timeInterval=setInterval(showRemainTimeInTitle,1000); } } function setRemainHTMLTag(text){ let id="smallsupo_remaintime"; let n=document.getElementById(id); if(n==null){ let node=document.querySelector('span.ytp-time-duration'); node=node.parentElement; let x=q.create("span",id,null); node.appendChild(x); }else{ n.innerText=text; } } function setRemainHTMLTagOnFullVideo(text,show){ let id="smallsupo_remaintime_fullvideo"; let n=document.getElementById(id); if(n==null){ let node=document.querySelector('div.html5-video-container'); node=node.parentElement; let x=q.create("div",id,null); x.setAttribute("style","position:absolute;z-index:999999;background-color:black;padding:4px;color:white;font-size:"+fontsize+";"); node.appendChild(x); }else{ if(show){ n.style.display="block"; n.innerText=text; }else{ n.style.display="none"; } } } function initStart(){ } function initAfterTitle(){ urlchanging=false; totaltime=getVideoTotalTime(); totaltime=HmsToMiliseconds(totaltime); remaintime=undefined; setRemainHTMLTag(""); startObserver(); } function init1(){ waitForElementObserver('#above-the-fold',(dom)=>{ let titlecontainer=dom.querySelector('yt-formatted-string'); title= titlecontainer.getAttribute("title");initAfterTitle(); console.log(title+" 1:"+totaltime); },()=>{title=document.title;console.log(title+" 2:"+totaltime);initAfterTitle();},5); //title=document.title; } function init(){ urlchanging=false; //title=document.title; totaltime=getVideoTotalTime(); //console.log(title+":"+totaltime); totaltime=HmsToMiliseconds(totaltime); remaintime=undefined; setRemainHTMLTag(""); startObserver(); } async function uninit(){ if(debug)console.log("uninit"); stopObserver(); //await delay(1000); stopTimeInterval(); } function stopObserver(){ if(observer!=null){ if(debug)console.log("stopObserver"); observer.disconnect();observer=null; } } function startObserver(){ if(debug)console.log("startObserver"); observer=new MutationObserver(runRemainTimer); let node=document.querySelector('#movie_player'); observer.observe(node,{attributes:true,childList:false,subtree:false,attributeFilter:['class']}); } let titleObserver=null; function stopTitleObserver(){ if(titleObserver!=null){ if(debug)console.log("stopTitleObserver"); titleObserver.disconnect();titleObserver=null; } } function startTitleObserver(dom){ if(debug)console.log("startTitleObserver"); titleObserver=new MutationObserver(()=>{title=dom.getAttribute('title');}); titleObserver.observe(dom,{attributes:true,childList:false,subtree:false,attributeFilter:['title']}); } //--------------------------------------- function watchUrlChange(callback) { let lastUrl = location.href; // ✅ DOM ready 後執行 callback function waitForDOMReady(cb) { if (document.readyState === 'complete' || document.readyState === 'interactive') { setTimeout(cb, 50); } else { window.addEventListener('DOMContentLoaded', () => cb(), { once: true }); } } // ✅ 檢查網址是否改變 const onUrlChange = () => { if (location.href !== lastUrl) { lastUrl = location.href; waitForDOMReady(callback); } }; // ✅ MutationObserver:觀察 DOM 是否有變動(例如 SPA 路由切換) const observer = new MutationObserver(onUrlChange); observer.observe(document, { childList: true, subtree: true }); // ✅ 捕捉 pushState / replaceState ['pushState', 'replaceState'].forEach(method => { const original = history[method]; history[method] = function () { original.apply(this, arguments); window.dispatchEvent(new Event('urlchange')); }; }); // ✅ 監聽瀏覽器前進/後退 或 push/replace 狀況 window.addEventListener('popstate', onUrlChange); window.addEventListener('urlchange', onUrlChange); // ✅ 初始觸發一次 waitForDOMReady(callback); } function waitForElementObserver(selector, functionX, functionY, timeoutSeconds) { let timeoutId = null; const observer = new MutationObserver(() => { const element = document.querySelector(selector); if (element) { if (timeoutId) clearTimeout(timeoutId); // 若有 timeout,清除它 observer.disconnect(); functionX(element); } }); observer.observe(document.body, { childList: true, subtree: true, }); const existing = document.querySelector(selector); if (existing) { if (timeoutId) clearTimeout(timeoutId); observer.disconnect(); functionX(existing); return; } // ✅ 只有在 functionY 和 timeoutSeconds 都有提供時才啟用 timeout 行為 if (typeof functionY === 'function' && typeof timeoutSeconds === 'number') { timeoutId = setTimeout(() => { observer.disconnect(); functionY(); }, timeoutSeconds * 1000); } } //-------------------------------------- const debug=false; function setUI(){ urlchanging=true; uninit(); if(/www.youtube.com\/watch/.test(window.location.href)){ waitForElementObserver('#above-the-fold',(dom)=>{ let titlecontainer=dom.querySelector('yt-formatted-string'); startTitleObserver(titlecontainer); }); //waitForElementObserver('#movie_player', waitForElementObserver('video', ()=>{ let timer; clearTimeout(timer); timer=setTimeout(function() { init(); }, 500); }); }else{ stopTitleObserver(); } } setTimeout(function() { const userAgent = navigator.userAgent; if (userAgent.includes("Firefox")) { console.log("Browser is Firefox"); if (window.trustedTypes && window.trustedTypes.createPolicy) { window.trustedTypes.createPolicy('default', { createHTML: (string, sink) => string }); } } console.log("Youtube-video remain time on tab title...啟動") title=document.title; watchUrlChange(setUI); }, 1000);
QingJ © 2025
镜像随时可能失效,请加Q群300939539或关注我们的公众号极客氢云获取最新地址