Enhanced Video Playing Experience for Viu.com

Automatically Full HD Video Quality (Paid Member) and Disable Auto Playing when the page is randomly reloaded

// ==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);