// ==UserScript==
// @name Enhanced Video Playing Experience for Viu.com
// @version 1.5
// @description Automatically Full HD Video Quality (Paid Member) and Disable Auto Playing when the page is randomly reloaded
// @match https://www.viu.com/ott/*/vod/*
// @icon https://www.google.com/s2/favicons?domain=viu.com
// @grant unsafeWindow
// @grant window.onurlchange
// @namespace https://greasyfork.org/users/371179
// ==/UserScript==
(function $$() {
'use strict';
if (!document || !document.documentElement) return window.requestAnimationFrame($$);
const uWin = window.unsafeWindow || window;
if ([document.hidden, window.requestAnimationFrame, Object.defineProperty, window.performance].some(x => x === undefined)) throw 'Your browser is too outdated.';
var navStart = performance.timeOrigin || performance.timing.navigationStart || null;
if (navStart === null) throw 'Your browser is too outdated.';
navStart = Math.ceil(navStart);
var lastPlayingStatus = null;
var settings = {}
var falseReloaded = false;
var lastUserClickAt = 0;
var lastUserClickStatus = 0;
var forceQuality = "1080";
const __jver__ = "20210617b";
const isPassiveOptionEnable = window.queueMicrotask?true:false; //here just a simple trick for checking browser is modern or not
function isVideoPlaying(video){
return video.currentTime > 0 && !video.paused && !video.ended && video.readyState > video.HAVE_CURRENT_DATA;
}
function pageInit() {
var _userPaused = null;
Object.defineProperty(settings, 'userPaused', {
get() {
return _userPaused;
},
set(nv) {
_userPaused = nv;
console.log('[viu] userPaused: ', _userPaused);
}
})
function doSomething(obj) {
//for viu page auto reload ( it is confirmed that the page could randomly reload)
if (!obj) return;
let {
version,
datetime,
pageVisible,
withVideo,
userPaused
} = obj;
if (version != __jver__) return;
const isReload = (t) => (t > 0 && t - 2 < navStart && t + 480 > navStart); // max 155ms => 155*3=465 => 480ms
if (isReload(datetime) && withVideo) {
console.log('[viu] reloaded page', obj)
if (userPaused || !pageVisible) settings.userPaused = true;
}
console.log('[viu] reload time diff: ' + (navStart - datetime) + ' => ' + isReload(datetime))
}
var _saveObj = localStorage.__zvpp_unload1__;
if (typeof _saveObj == 'string' && _saveObj.length > 0) {
var _jObj = null;
try {
_jObj = JSON.parse(_saveObj);
} catch (e) {}
if (_jObj) {
doSomething(_jObj);
}
}
delete localStorage.__viu_js_fewunaznqjrx__
delete localStorage.__viu_js_fewunaznqjr2__
delete localStorage.__viu_js_fewunaznqjra__
delete localStorage.__zvpp_unload1__
if (localStorage.__zvpp_ver__ !== __jver__) {
for (var k in localStorage) {
if (k.indexOf('__zvpp_') === 0) localStorage.removeItem(k)
}
localStorage.__zvpp_ver__ = __jver__
}
}
function getVideoFromEvent(evt) {
return (evt && evt.target && evt.target.nodeName == 'VIDEO') ? evt.target : null;
}
function hasOffsetParent(v) {
return v && v.offsetParent !== null && v.offsetParent.nodeType === 1; //is DOM valid on the page
}
function noAutoStart(evt) {
const video = getVideoFromEvent(evt);
if (video) {
if (lastUserClickStatus===2) {
//do nothing for user click
} else if (settings.userPaused === true) {
video.autoplay = false;
video.pause();
} else {
if (lastPlayingStatus === true && document.hidden === true) {} else if (document.hidden === true) {
video.autoplay = false;
video.pause();
}
}
}
}
const delayCall = function(p, f, d) {
if (delayCall[p] > 0) clearTimeout(delayCall[p])
delayCall[p] = setTimeout(f, d)
}
function loader(detection) {
return function() {
let oldHref = document.location.href,
bodyDOM = document.querySelector("body");
const observer = new MutationObserver(function(mutations) {
if (oldHref != document.location.href) {
oldHref = document.location.href;
detection();
window.requestAnimationFrame(function() {
let tmp = document.querySelector("body");
if (tmp != bodyDOM) {
bodyDOM = tmp;
observer.observe(bodyDOM, config);
}
})
}
});
const config = {
childList: true,
subtree: true
};
observer.observe(bodyDOM, config);
}
}
const anyRecentClick=()=>lastUserClickAt + 5000 > +new Date;
function pageEnableAutoQualityAtStart() {
//default 1080p
var cid = 0;
var mDate = 0;
const TIMEOUT = 8000; // just in case DOM is not found
var gn = function(evt) {
const video = getVideoFromEvent(evt);
if (!video) return;
delayCall('$$video_init', function() {
if (video.hasAttribute('_viu_js_hooked')) return;
video.setAttribute('_viu_js_hooked', '')
video.addEventListener('playing', function(evt) {
const video = getVideoFromEvent(evt);
if (!video) return;
delayCall('$$video_playing_switch', function() {
if (video.paused === false && hasOffsetParent(video)) {
if (document.hidden !== true && settings.userPaused === true) settings.userPaused = false;
}
}, 300);
}, true)
video.addEventListener('pause', function(evt) {
const video = getVideoFromEvent(evt);
if (!video) return;
delayCall('$$video_playing_switch', function() {
if (video.paused === true && hasOffsetParent(video)) {
if (document.hidden !== true && (settings.userPaused === null || settings.userPaused === false)) settings.userPaused = true;
}
}, 300);
}, true)
}, 800);
//call when the first video event fires
if(lastUserClickStatus===1 && lastUserClickAt+5000 > +new Date){
//url change: 2598 1443 1542 1975
//without url change : 561
console.log(`[viu] click and video status change ${+new Date -lastUserClickAt}`)
//user perform action and video status changed
lastUserClickStatus=2;
}
if(lastUserClickStatus!=2){
// clear lastUserClickStatus
delayCall('$$user_click_action_w38',function(){
if(lastUserClickStatus!==2) lastUserClickStatus=0;
},300)
}else{
// extend duration of status 2 for 800ms
delayCall('$$user_click_action_w38',function(){
if(lastUserClickStatus===2) lastUserClickStatus=0;
},800)
}
//disable autostart
noAutoStart(evt);
mDate = +new Date + TIMEOUT;
var jn = function(btn1080) {
//button is found
const bool = btn1080.matches(':not([aria-disabled=""]):not([aria-disabled="true"]):not([aria-checked="true"]):not([aria-checked=""])');
console.log('resolution button found');
if (bool) {
Promise.resolve()
.then(()=>new Promise(r=>{
const tf = function(){
let bool = document.querySelectorAll('button[id^="resolution_"]').length>=2 && document.querySelectorAll('button[id^="resolution_"].vjs-selected').length===1
if(!bool)return setTimeout(tf,30);
let video = document.querySelector('video[id*="-video-viu-player"][src]')
if(!video)return setTimeout(tf,30);
const bReady = video.currentTime>0 && !video.ended && video.readyState>video.HAVE_CURRENT_DATA;
if(!bReady)return setTimeout(tf,30);
console.log('video ready');
r();
}
tf();
}))
.then(()=>{
let btn=document.querySelector('button#resolution_list.bmpui-ui-qualitysettingstogglebutton[aria-pressed]');
if(btn){
btn.dispatchEvent(new Event('mouseenter'))
btn.className.replace(/\b(bmpui-off)\b/,'bmpui-on');
btn.setAttribute('aria-pressed','true');
}
btn1080.dispatchEvent(new Event('mouseenter'))
})
.then(()=>new Promise((resolve)=>setTimeout(resolve,8)))
.then(()=>btn1080.click())
.then(()=>new Promise((resolve)=>setTimeout(resolve,20)))
.then(()=>{
btn1080.dispatchEvent(new Event('mouseleave'))
let btn=document.querySelector('button#resolution_list.bmpui-ui-qualitysettingstogglebutton[aria-pressed]');
if(btn){
btn.dispatchEvent(new Event('mouseleave'))
btn.className.replace(/\b(bmpui-on)\b/,'bmpui-off');
btn.setAttribute('aria-pressed','false');
}
})
.then(()=>new Promise((resolve)=>setTimeout(resolve,8)))
.then(()=>{
let pElm=btn1080;
let menuUI=null;
while(pElm && pElm.parentNode){
let checkClsName=pElm.className.replace(/\b(bmpui-ui-settings-panel|customize-video-option-panel)\b/gi,'@@');
checkClsName=checkClsName.replace(/\b[a-zA-Z0-9_\-]+\b/gi,'').replace(/\s+/g,' ').trim();
if(checkClsName=='@@ @@'){
menuUI=pElm;
break;
}
pElm=pElm.parentNode;
}
if(menuUI){
if(!/\b(bmpui-hidden)\b/.test(menuUI.className)) menuUI.className=menuUI.className.trim()+' bmpui-hidden';
}
})
.catch((e)=>0)
}
}
var zn = function() {
//query when the video is loading/loaded/ready...
if (cid > 0 && mDate < +new Date) {
cid = clearInterval(cid);
return;
}
var btn1080 = document.querySelector(`button[id^="resolution_${forceQuality}"]`)
//var btn1080 = document.querySelector(`.vjs-menu-item[data-r="${forceQuality}"]`);
if (!btn1080) return;
if (cid > 0) cid = clearInterval(cid);
if (btn1080.matches('[__userscript_viu_loaded]')) return true;
btn1080.setAttribute('__userscript_viu_loaded', 'true');
window.requestAnimationFrame(() => jn(btn1080)); // prevent too fast
}
if (cid > 0) cid = clearInterval(cid);
if (!zn()) cid = setInterval(zn, 33);
}
document.addEventListener('loadstart', gn, true)
document.addEventListener('durationchange', gn, true)
document.addEventListener('loadedmetadata', gn, true)
document.addEventListener('loadeddata', gn, true)
//document.addEventListener('progress', gn, true)
document.addEventListener('canplay', gn, true)
//document.addEventListener('canplaythrough', gn, true)
document.addEventListener('playing',function(evt){
if(!evt||!evt.target||evt.target.nodeName!="VIDEO")return;
let video = evt.target;
requestAnimationFrame(()=>{
if(!isVideoPlaying(video)) return;
let unmuteBtn = document.querySelector('button.bmpui-unmute-button.unmute-button.bmpui-muted');
if(video.muted && unmuteBtn) unmuteBtn.click();
})
},true)
}
const detection1 = function() {
console.log('[viu] viu.com reloaded url - detection #1')
}
const detection2 = function() {
console.log('[viu] viu.com reloaded url - detection #2')
}
const detection3 = function(info) {
console.log('[viu] viu.com reloaded url - detection #3', info)
}
function handleBeforeUnload(event) {
//core event handler for detection of false reloading
console.log('[viu] viu.com reloaded url - detection #4')
const video = document.querySelector('video#viu-player_html5_api') || document.querySelector('video');
var saveObj = {};
saveObj.version = __jver__;
saveObj.datetime = +new Date();
saveObj.pageVisible = !(document.hidden === true);
saveObj.withVideo = (video && video.nodeName == "VIDEO")
saveObj.userPaused = (settings.userPaused === true);
localStorage.__zvpp_unload1__ = JSON.stringify(saveObj);
navStart = (+new Date) - 1;
// Cancel the event
//event.preventDefault();
//return (event.returnValue = ""); // Legacy method for cross browser support
}
function handleVisibilityChange() {
//enable if document.hidden exists
const video = document.querySelector('video#viu-player_html5_api') || document.querySelector('video'); // just in case
if (!video) lastPlayingStatus = null;
if (document.hidden === false) {
console.log('[viu] page show')
} else if (document.hidden === true) {
console.log('[viu] page hide')
if (video) lastPlayingStatus = !video.paused
} else {
lastPlayingStatus = null;
}
}
function isMenuBtn(evt) {
return evt && evt.target && evt.target.nodeType === 1 && (evt.target.className || "").indexOf('vjs-menu-item') === 0;
}
function setQualityAfterClick() {
delayCall('$$change_video_quality', function() {
let selectedQuality=-1
let elm = document.querySelector('button[id^="resolution_"].vjs-selected');
if(!elm)return;
let regexp=/\d+/.exec(elm.id);
if(regexp){
selectedQuality = regexp[0];
}
//let attr = document.querySelector('.vjs-menu-item.vjs-selected[data-r]').getAttribute('data-r');
if (+selectedQuality > 0) {
forceQuality = (+selectedQuality).toString();
console.log(`[viu] video quality set at ${selectedQuality}`)
}
}, 300)
}
pageInit();
pageEnableAutoQualityAtStart();
if (window.onurlchange === null) window.addEventListener('urlchange', detection3, true); // feature is supported
window.addEventListener("load", loader(detection1), true);
uWin.addEventListener("load", loader(detection2), true);
uWin.addEventListener("beforeunload", handleBeforeUnload, true);
if (typeof document.hidden !== "undefined") document.addEventListener("visibilitychange", handleVisibilityChange, true);
function handleMouseDown(evt) {
lastUserClickAt = +new Date;
lastUserClickStatus =1;
delayCall('$$user_click_action_w38',function(){
if(lastUserClickStatus===1) lastUserClickStatus=0;
},5000)
if (isMenuBtn(evt)) setQualityAfterClick();
}
document.addEventListener("mousedown", handleMouseDown, isPassiveOptionEnable?{
passive: true,
capture: true
}:true)
function onReady(){
setTimeout(function(){
for(const s of document.querySelectorAll('link[rel="stylesheet"][href*=".css"]')){
if(!s.hasAttribute('type')) s.setAttribute('type','text/css');
if(!s.hasAttribute('charset')) s.setAttribute('charset','utf-8');
}
},40);
}
if (document.readyState != 'loading') {
onReady();
} else {
window.addEventListener("DOMContentLoaded", onReady, false);
}
// Your code here...
})(unsafeWindow || window);