Greasy Fork is available in English.

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