// ==UserScript==
// @name Youtube-video remaining time on tab title
// @namespace smallsupo
// @version 1.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
// @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;
const debug=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;
let n=q.attribute(document,'div#movie_player',"class");
if(n!=null)if(n.indexOf("updated-full-mode")!=-1)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 init(){
urlchanging=false;
if(debug)console.log("init");
if(/www.youtube.com\/watch/.test(window.location.href)){
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']});
}
function start_page_interval(){
let timer;
//console.log("start_page_interval");
setInterval(()=>{
if (window.location.href !== currenturl) {
urlchanging=true;
currenturl=window.location.href;
//console.log("url changed");
uninit();
clearTimeout(timer);
timer=setTimeout(function() {
init();
}, 3000);
}
}, 1000);
}
$(document).ready(function() {
console.log("Youtube-video remain time on tab title...啟動")
start_page_interval();
});