// ==UserScript==
// @name YouTube Live: Auto RealTime
// @namespace UserScripts
// @match https://www.youtube.com/*
// @grant none
// @version 0.2.12
// @author CY Fung
// @description 1/13/2024, 7:11:55 PM
// @run-at document-start
// @license MIT
//
// ==/UserScript==
(() => {
let byPassPlaybackRate = false;
let tmpPlaybackRate = null;
if (typeof AbortSignal === 'undefined') return;
// const nextBrowserTick = (self || 0).nextBrowserTick || 0;
let __requestAnimationFrame__ = typeof webkitRequestAnimationFrame === 'function' ? window.webkitRequestAnimationFrame.bind(window) : window.requestAnimationFrame.bind(window);
// if (!nextBrowserTick) return;
let instance = null;
/** @type {globalThis.PromiseConstructor} */
const Promise = (async () => { })().constructor; // YouTube hacks Promise in WaterFox Classic and "Promise.resolve(0)" nevers resolve.
const PromiseExternal = ((resolve_, reject_) => {
const h = (resolve, reject) => { resolve_ = resolve; reject_ = reject };
return class PromiseExternal extends Promise {
constructor(cb = h) {
super(cb);
if (cb === h) {
/** @type {(value: any) => void} */
this.resolve = resolve_;
/** @type {(reason?: any) => void} */
this.reject = reject_;
}
}
};
})();
const observablePromise = (proc, timeoutPromise) => {
let promise = null;
return {
obtain() {
if (!promise) {
promise = new Promise(resolve => {
let mo = null;
const f = () => {
let t = proc();
if (t) {
mo.disconnect();
mo.takeRecords();
mo = null;
resolve(t);
}
}
mo = new MutationObserver(f);
mo.observe(document, { subtree: true, childList: true })
f();
timeoutPromise && timeoutPromise.then(() => {
resolve(null)
});
});
}
return promise
}
}
}
let fc = 0;
// const pNextBrowserTick = () => new Promise(resolve => nextBrowserTick(resolve));
const pd = Object.getOwnPropertyDescriptor(HTMLMediaElement.prototype, 'playbackRate');
const isPassiveArgSupport = (typeof IntersectionObserver === 'function');
const bubblePassive = isPassiveArgSupport ? { capture: false, passive: true } : false;
const capturePassive = isPassiveArgSupport ? { capture: true, passive: true } : true;
const insp = o => o ? (o.polymerController || o.inst || o || 0) : (o || 0);
let pageFetchedDataLocal = null;
document.addEventListener('yt-page-data-fetched', (evt) => {
pageFetchedDataLocal = evt.detail;
}, bubblePassive);
function getFormatDates() {
if (!pageFetchedDataLocal) return null;
const formatDates = {}
try {
formatDates.publishDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.publishDate
} catch (e) { }
// 2022-12-30
try {
formatDates.uploadDate = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.uploadDate
} catch (e) { }
// 2022-12-30
try {
formatDates.publishDate2 = pageFetchedDataLocal.pageData.response.contents.twoColumnWatchNextResults.results.results.contents[0].videoPrimaryInfoRenderer.dateText.simpleText
} catch (e) { }
// 2022/12/31
if (typeof formatDates.publishDate2 === 'string' && formatDates.publishDate2 !== formatDates.publishDate) {
formatDates.publishDate = formatDates.publishDate2
formatDates.uploadDate = null
}
try {
formatDates.broadcastBeginAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.startTimestamp
} catch (e) { }
try {
formatDates.broadcastEndAt = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.endTimestamp
} catch (e) { }
try {
formatDates.isLiveNow = pageFetchedDataLocal.pageData.playerResponse.microformat.playerMicroformatRenderer.liveBroadcastDetails.isLiveNow
} catch (e) { }
return formatDates;
}
const promiseVideoNextFn = async (video, v) => {
if (typeof video.requestVideoFrameCallback === 'function' && v !== false) {
return true === await Promise.race([
new Promise(resolve => video.requestVideoFrameCallback(() => {
resolve(true);
})),
new Promise(resolve => setTimeout(resolve, 1000))
]);
} else {
return true === await Promise.race([
new Promise(resolve => video.addEventListener('timeupdate', () => {
resolve(true);
}, { once: true, passive: true, capture: false })),
new Promise(resolve => setTimeout(resolve, 1000))
]);
}
}
let promisePR1;
let videoTarget = null;
let pr01 = new PromiseExternal();
let pr02 = new PromiseExternal();
document.addEventListener('durationchange', function (evt) {
const target = (evt || 0).target;
if (!(target instanceof HTMLVideoElement)) return;
if (target.classList.contains('video-stream') && target.classList.contains('html5-main-video')) {
videoTarget = target;
if (target.duration !== 3600 && target.duration > 120) {
// console.log(1233)
pr01.resolve();
pr01 = new PromiseExternal();
}
}
}, true);
async function run(pageFetchedDataLocal) {
if (fc > 1e9) fc = 9;
let tc = ++fc;
const timeout = new Promise(r => setTimeout(r, 1000));
/*
// console.log(10001)
const watchPages = [...document.querySelectorAll('ytd-watch-flexy')].filter(e => !e.closest('[hidden]'));
if (watchPages.length !== 1 || !watchPages[0]) return;
const watchPage = watchPages[0];
// console.log(10002)
const timeout = new Promise(r => setTimeout(r, 1000));
const liveBtn = await observablePromise(() => watchPage.querySelector('button.ytp-live-badge.ytp-button[disabled]'), timeout).obtain();
if (tc !== fc) return;
// console.log(10003)
if (!liveBtn) return;
// console.log(10004)
if (liveBtn.closest('[hidden]')) return;
// console.log(10005)
if (!liveBtn.matches('.ytp-chrome-controls .ytp-live .ytp-live-badge')) return;
// console.log(10006)
await new Promise((resolve) => __requestAnimationFrame__(resolve));
if (tc !== fc) return;
// console.log(10007)
if (!liveBtn.isConnected) return;
// console.log(10008)
const settingBtn = watchPage.querySelector('button.ytp-button.ytp-settings-button:not([disabled])');
// console.log(10009)
if (!settingBtn) return;
if (settingBtn.closest('[hidden]')) return;
*/
// console.log(100010)
//
// console.log(10001)
const video = videoTarget;
// const video = watchPage.querySelector('video.video-stream.html5-main-video');
if (!video) return;
if (video.paused !== false || video.isConnected !== true) return;
// console.log(10011)
if (!pageFetchedDataLocal) return;
const dates = getFormatDates();
if (!dates) return;
// console.log(dates)
// console.log(10002, video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4, video.currentTime > 0.1 && video.duration > 0.1, instance)
const fn = () => {
if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) return false;
return video.currentTime > 0.1 && video.duration > 0.1 && instance;
}
if (!fn()) {
await observablePromise(fn, timeout).obtain();
}
// console.log(10003)
if (tc !== fc) return;
await new Promise(resolve => video.addEventListener('timeupdate', () => {
resolve();
}, { once: true, passive: true, capture: false }));
// console.log(10004)
if (tc !== fc) return;
// await new Promise((resolve) => __requestAnimationFrame__(resolve));
if (dates.broadcastBeginAt && !dates.broadcastEndAt && dates.isLiveNow === true) {
} else {
return;
}
// console.log(10005)
let setTime = null;
if (dates.broadcastBeginAt && !dates.broadcastEndAt && dates.isLiveNow === true) {
let t1 = video.currentTime;
let t2 = (new Date() - new Date(dates.broadcastBeginAt)) / 1000;
let te = video.duration;
// console.log(12342, t1,t2)
// console.log(12343, t1>0.8 , t2>0.8 , te>0.8 , t2-t1> 0.6 , te > t2 + 3.0 , te > t1 + 3.0)
if (t1 > 0.8 && t2 > 0.8 && te > 0.8 && te > t2 + 3.0 && te > t1 + 3.0) {
// await new Promise((resolve) => requestAnimationFrame(resolve));
// pd.set.call(video, 1.0);
if (t2 - t1 > 0.6) {
setTime = t2 - 0.49;
}
// console.log(4320, instance)
// instance.setCurrentTime(t2, true);
// setInterval(() => {
// console.log(new Date(), video.currentTime, (new Date() - new Date(dates.broadcastBeginAt)) / 1000)
// }, 1000);
}
}
let crate = pd.get.call(video)
if (!(crate > 0.99 && crate < 1.01)) return;
// pd.set.call(video, 1);
if (typeof setTime === 'number') {
// video.currentTime = setTime;
}
// console.log('yyee')
for (let i = 0; i < 3; i++) {
promisePR1 = null;
// console.log(3170)
if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) return;
if (false === await promiseVideoNextFn(video)) return;
if (tc !== fc) return;
// console.log(3171)
byPassPlaybackRate = true;
try {
const pr = promisePR1 = new PromiseExternal();
let rate = 1;
if (i === 0) rate = 13.52 + Math.random() * 0.45;
else if (i === 1) rate = 1.26 + Math.random() * 0.22;
else rate = 1.06 + Math.random() * 0.01;
// console.log(3172)
rate = +rate.toFixed(5);
console.log('rate', rate)
instance.setPlaybackRate(rate, true);
if (video.paused || !video.isConnected || video.networkState !== 2 || video.readyState !== 4) {
} else {
await promiseVideoNextFn(video);
}
// console.log(3173, video.paused , !video.isConnected , video.networkState !== 2 , video.readyState !== 4)
await pr.then();
// console.log(3174)
await promiseVideoNextFn(video);
await promiseVideoNextFn(video, false);
} finally {
byPassPlaybackRate = false;
}
// console.log(3175)
}
/*
console.log(12312)
console.log()
if (tc !== fc) return;
settingBtn.click();
await pNextBrowserTick();
let menuItemBtns = [...watchPage.querySelectorAll('.ytp-panel .ytp-menuitem[aria-haspopup]')].filter(e => {
return e.querySelector('[d^="M10,8v8l6-4L10,"]')
});
if (menuItemBtns.length !== 1 || !menuItemBtns[0]) return;
let menuItemBtn = menuItemBtns[0]
menuItemBtn.click();
await pNextBrowserTick();
let radioBtns = [...watchPage.querySelectorAll('.ytp-popup.ytp-settings-menu .ytp-panel-menu .ytp-menuitem[role="menuitemradio"]')].filter(e => {
return e.textContent.trim() === '2'
});
if (radioBtns.length !== 1 || !radioBtns[0]) return;
let radioBtn = radioBtns[0];
radioBtn.click();
await pNextBrowserTick();
pd.set.call(video, 8.98);
if (radioBtn.isConnected) settingBtn.click();
*/
// console.log(1232131)
}
document.addEventListener('yt-navigate-finish', () => {
pr02.resolve();
// console.log(1234)
pr02 = new PromiseExternal();
}, false);
(async () => {
let lastSrc = null;
while (1) {
await Promise.all([pr01, pr02]);
if (!videoTarget || !videoTarget.isConnected) return;
// console.log(12332)
let src = videoTarget.src;
if (lastSrc !== src) {
lastSrc = src;
run(pageFetchedDataLocal);
}
await new Promise(resolve => __requestAnimationFrame__(resolve));
}
})();
const _yt_player_observable = observablePromise(() => {
return (((window || 0)._yt_player || 0) || 0);
});
(async () => {
const _yt_player = await _yt_player_observable.obtain();
if (!_yt_player || typeof _yt_player !== 'object') return;
const addProtoToArr = (parent, key, arr) => {
let isChildProto = false;
for (const sr of arr) {
if (parent[key].prototype instanceof parent[sr]) {
isChildProto = true;
break;
}
}
if (isChildProto) return;
arr = arr.filter(sr => {
if (parent[sr].prototype instanceof parent[key]) {
return false;
}
return true;
});
arr.push(key);
return arr;
}
const getGU = (_yt_player) => {
const w = 'GU';
let arr = [];
for (const [k, v] of Object.entries(_yt_player)) {
const p = typeof v === 'function' ? v.prototype : 0;
if (p
&& typeof p.setPlaybackRate === 'function' && p.setPlaybackRate.length === 2
&& typeof p.getPlaybackRate === 'function' && p.getPlaybackRate.length === 0
// && typeof p.isAtLiveHead === 'function'
&& typeof p.getVideoUrl === 'function' && p.getVideoUrl.length === 4
&& typeof p.getCurrentTime === 'function' && p.getCurrentTime.length == 2
&& typeof p.getDuration === 'function' && p.getDuration.length == 2
&& !(typeof p.isPaused === 'function' && p.isPaused.length === 0
&& typeof p.getCurrentTime === 'function' && p.getCurrentTime.length === 0
&& typeof p.getDuration === 'function' && p.getDuration.length === 0
// && typeof p.isAtLiveHead === 'function' && p.isAtLiveHead.length === 0
&& typeof p.getVideoPlaybackQuality === 'function' && p.getVideoPlaybackQuality.length === 0
&& typeof p.stopVideo === 'function' && p.stopVideo.length === 0)
// && Object.keys(_yt_player[k].prototype).includes('addEventListener')
// && !p.dispose && !p.isDisposed
) {
arr = addProtoToArr(_yt_player, k, arr) || arr;
}
}
// console.log(1222, arr.map(k=> Object.keys(_yt_player[k].prototype).sort()))
if (arr.length === 0) {
console.warn(`Key does not exist. [${w}]`);
} else {
console.log(`[${w}]`, arr);
return arr[0];
}
}
const getW0 = (_yt_player) => {
const w = 'W0';
let arr = [];
for (const [k, v] of Object.entries(_yt_player)) {
const p = typeof v === 'function' ? v.prototype : 0;
if (p
&& typeof p.isAtLiveHead === 'function'
&& typeof p.seekTo === 'function'
&& typeof p.loadVideoByPlayerVars === 'undefined'
) {
arr = addProtoToArr(_yt_player, k, arr) || arr;
}
}
// console.log(1222, arr.map(k=> _yt_player[k].prototype['isAtLiveHead']))
// console.log(1223, arr.map(k=> Object.keys( _yt_player[k].prototype)))
if (arr.length === 0) {
console.warn(`Key does not exist. [${w}]`);
} else {
console.log(`[${w}]`, arr);
return arr[0];
}
}
const getg1 = (_yt_player) => {
const w = 'g1';
let arr = [];
for (const [k, v] of Object.entries(_yt_player)) {
const p = typeof v === 'function' ? v.prototype : 0;
if (p
&& typeof p.canPlayType === 'function' && p.canPlayType.length === 1
&& typeof p.cancelPlayback === 'function' && p.cancelPlayback.length === 2
&& typeof p.getInternalApi === 'function' && p.getInternalApi.length === 0
&& typeof p.getAppState === 'function' && p.getAppState.length === 0
&& typeof p.getPresentingPlayerType === 'function' && p.getPresentingPlayerType.length === 1
&& typeof p.getVideoData === 'function' && p.getVideoData.length === 0
&& typeof p.seekTo === 'function'
&& typeof p.seekBy === 'function'
&& typeof p.sendVideoStatsEngageEvent === 'function'
) {
arr = addProtoToArr(_yt_player, k, arr) || arr;
}
}
// console.log(1222, arr.map(k=> _yt_player[k].prototype['isAtLiveHead']))
// console.log(1223, arr.map(k=> Object.keys( _yt_player[k].prototype)))
if (arr.length === 0) {
console.warn(`Key does not exist. [${w}]`);
} else {
console.log(`[${w}]`, arr);
return arr[0];
}
}
const key = getGU(_yt_player);
// console.log(1233, key)
const g = _yt_player;
const k = key;
const gk = g[k];
const gkp = g[k].prototype;
// gkp.isAtLiveHead322 = gkp.isAtLiveHead;
// gkp.isAtLiveHead = function(){
// // console.log(5555)
// instance = this;
// return this.isAtLiveHead322();
// }
gkp.getPlaybackRate322 = gkp.getPlaybackRate;
gkp.getPlaybackRate = function () {
// console.log(5556, this.getPlaybackRate322)
instance = this;
return this.getPlaybackRate322();
}
gkp.setPlaybackRate322 = gkp.setPlaybackRate;
gkp.setPlaybackRate = function (a, b) {
instance = this;
if (byPassPlaybackRate) {
// console.log(5888, 12333,a, b)
// return;
}
// console.log(5388, arguments)
return this.setPlaybackRate322(a, b);
}
let key2 = getW0(_yt_player);
// console.log('XXA', key2)
let key3 = getg1(_yt_player);
// console.log('XXB', key3)
/*
_yt_player[key3].prototype.E8xx = _yt_player[key3].prototype.E8;
_yt_player[key3].prototype.E8 = function(a){
let b1= g.sH(a, 1);
let b2= !g.qH(a.state, 64)
let b3= this.Hd().isLivePlayback;
let b4= this.zb.isAtLiveHead();
let b5= 1 < this.Va.getPlaybackRate();
console.log('XXPP0', a)
console.log('XXPP1' , b1,b2,b3,b4,b5)
setTimeout(()=>{
let b1= g.sH(a, 1);
let b2= !g.qH(a.state, 64)
let b3= this.Hd().isLivePlayback;
let b4= this.zb.isAtLiveHead();
let b5= 1 < this.Va.getPlaybackRate();
// console.log('XXPP2' , b1,b2,b3,b4,b5)
}, 1000)
return this.E8xx(a);
}
*/
})();
Storage.prototype.setItem322 = Storage.prototype.setItem;
Storage.prototype.setItem = function (a, b) {
if (a === 'yt-player-playback-rate') {
tmpPlaybackRate = b;
// if(!byPassPlaybackRate) debugger
if (promisePR1 && b && typeof b === 'string' && b.indexOf('{"data":"1"') >= 0) {
promisePR1.resolve();
promisePR1 = null;
}
// console.log(5883, a, b,byPassPlaybackRate, 'XX_'+tmpPlaybackRate+'_', location.pathname)
if (window.location.pathname === '/live_chat') return;
}
if (byPassPlaybackRate && a === 'yt-player-playback-rate') return;
this.setItem322(a, b);
}
Storage.prototype.getItem322 = Storage.prototype.getItem;
Storage.prototype.getItem = function (a) {
if (a === 'yt-player-playback-rate') {
if (window.location.pathname === '/live_chat') return null;
// console.log(5884, a, 'XX_'+tmpPlaybackRate+'_', location.pathname)
if (typeof tmpPlaybackRate === 'string') return tmpPlaybackRate;
}
if (a === 'yt-player-playback-rate' && typeof tmpPlaybackRate === 'string') return tmpPlaybackRate;
return this.getItem322(a);
}
Object.defineProperty(Storage.prototype, 'yt-player-playback-rate', {
get() {
// console.log(5881, 'XX_'+tmpPlaybackRate+'_', location.pathname)
return this.getItem('yt-player-playback-rate');
},
set(nv) {
// console.log(5882, nv, 'XX_'+tmpPlaybackRate+'_', location.pathname);
this.setItem('yt-player-playback-rate', nv);
return true;
},
enumerable: true,
configurable: true
});
})();