HTML5 video settings

Change on load default HTML5 video behavior

// ==UserScript==
// @name        HTML5 video settings
// @name:ru     Настройки HTML5 видео
// @namespace   html5-video-settings
// @author      smut
// @version     2020.09.30.1
// @icon https://img.icons8.com/color/344/video.png
// @description Change on load default HTML5 video behavior
// @description:ru Изменение дефолтных параметров воспроизведения HTML5 видео
// @grant        GM_log
// @grant        GM_getValue
// @grant        GM_setValue
// @grant        GM_listValues
// @grant        GM_registerMenuCommand
// @grant        GM.cookie
// @grant        GM_util
// @grant        GM_util.timeout
// @grant        unsafeWindow
// @grant        window.close
// @exclude      /^https?:\/\/([^.]+\.)*?(youtube\.com|coub\.com|youtu\.be|pikabu\.ru)([:/]|$)/
// @match *://*/*
// ==/UserScript==

(function() {

    'use strict';

    const win = (unsafeWindow || window);

    const
        _Document = Object.getPrototypeOf(HTMLDocument.prototype),
        _Element = Object.getPrototypeOf(HTMLElement.prototype);
    const
        _Node = Object.getPrototypeOf(_Element);

    const
        isSafari =
            Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
            (function (p) {
                return p.toString() === "[object SafariRemoteNotification]";
            })(!window.safari || window.safari.pushNotification),
        isFirefox = 'InstallTrigger' in win,
        inIFrame = (win.self !== win.top);
    const
        _bindCall = fun => Function.prototype.call.bind(fun),
        _getAttribute = _bindCall(_Element.getAttribute),
        _setAttribute = _bindCall(_Element.setAttribute),
        _removeAttribute = _bindCall(_Element.removeAttribute),
        _hasOwnProperty = _bindCall(Object.prototype.hasOwnProperty),
        _toString = _bindCall(Function.prototype.toString),
        _document = win.document,
        _de = _document.documentElement,
        _appendChild = _Document.appendChild.bind(_de),
        _removeChild = _Document.removeChild.bind(_de),
        _createElement = _Document.createElement.bind(_document),
        _querySelector = _Document.querySelector.bind(_document),
        _querySelectorAll = _Document.querySelectorAll.bind(_document),
        _attachShadow = ('attachShadow' in _Element) ? _bindCall(_Element.attachShadow) : null,
        _apply = Reflect.apply,
        _construct = Reflect.construct;

    let skipLander = true;
    try {
        skipLander = !(isFirefox && 'StopIteration' in win);
    } catch (ignore) {}

    const jsf = (function () {
        const opts = {};
        let getValue = (a, b) => b,
            setValue = () => null,
            listValues = () => [];
        try {
            [getValue, setValue, listValues] = [GM_getValue, GM_setValue, GM_listValues];
        } catch (ignore) {}
        // defaults
        opts.Lang = 'eng';
        opts.controls = true;
        opts.loop = false;
        opts.autoplay = false;
        opts.muted = false;
        // load actual values
        for (let name of listValues())
            opts[name] = getValue(name, opts[name]);
        const checkName = name => {
            if (!_hasOwnProperty(opts, name))
                throw new Error('Attempt to access missing option value.');
            return true;
        };
        return new Proxy(opts, {
            get(opts, name) {
                if (name === 'toString')
                    return () => JSON.stringify(opts);
                if (checkName(name))
                    return opts[name];
            },
            set(opts, name, value) {
                if (checkName(name)) {
                    opts[name] = value;
                    setValue(name, value);
                }
                return true;
            }
        });
    })();

    if (isFirefox && _document.constructor.prototype.toString() === '[object ImageDocumentPrototype]')
        return;

    if (!NodeList.prototype[Symbol.iterator])
        NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
    if (!HTMLCollection.prototype[Symbol.iterator])
        HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];

    if (GM.cookie === undefined)
        GM.cookie = {
            list: () => ({
                then: () => null
            })
        };

    const
        batchLand = [],
        batchPrepend = new Set(),
        _APIString = `const win = window, isFirefox = ${isFirefox}, inIFrame = ${inIFrame}, _document = win.document, _de = _document.documentElement,
        _Document = Object.getPrototypeOf(HTMLDocument.prototype), _Element = Object.getPrototypeOf(HTMLElement.prototype), _Node = Object.getPrototypeOf(_Element),
        _appendChild = _Document.appendChild.bind(_de), _removeChild = _Document.removeChild.bind(_de), skipLander = ${skipLander},
        _createElement = _Document.createElement.bind(_document), _querySelector = _Document.querySelector.bind(_document),
        _querySelectorAll = _Document.querySelectorAll.bind(_document), _bindCall = fun => Function.prototype.call.bind(fun),
        _getAttribute = _bindCall(_Element.getAttribute), _setAttribute = _bindCall(_Element.setAttribute),
        _removeAttribute = _bindCall(_Element.removeAttribute), _hasOwnProperty = _bindCall(Object.prototype.hasOwnProperty),
        _toString = _bindCall(Function.prototype.toString), _apply = Reflect.apply, _construct = Reflect.construct;
        const GM = { info: { version: '0.0', scriptHandler: null }, cookie: { list: () => ({ then: () => null }) } };
        const jsf = ${jsf.toString()}`,
        landScript = (f, pre) => {
            const script = _createElement('script');
            script.textContent = `(()=>{${_APIString}${[...pre].join(';')};(${f.join(')();(')})();})();`;
            _appendChild(script);
            _removeChild(script);
        },
        startdelay = 2000,
        clickdelay = 1000,
        playdelay = 200;
    var first_load_mute = false;
    var play_click_iframe = false;
    var play_click_timeout;

    let scriptLander = f => f();
    if (!skipLander) {
        scriptLander = (func, ...prepend) => {
            prepend.forEach(x => batchPrepend.add(x));
            batchLand.push(func);
        };
        _document.addEventListener(
            'DOMContentLoaded', () => void(scriptLander = (f, ...prep) => landScript([f], prep)), false
        );
    }

    function play_click_switch(play_switch) {
        play_click_iframe = play_switch;
    }
    function html5_video_set(play_click) {
        for (var e of document.getElementsByTagName('video')){
            if (jsf.controls){
                e.setAttribute('controls', '');
                e.controls = "controls";
                if (play_click){
                    if (window.location.hostname === 'www.instagram.com'){
                        var instaoverlay = document.querySelector('.PyenC');
                        var isntacontrol = document.querySelector('.fXIG0');
                        //console.log (instaoverlay);
                        if(document.querySelector('.PyenC')){
                            instaoverlay.parentNode.removeChild(instaoverlay);
                        }
                        if(document.querySelector('.fXIG0')){
                            isntacontrol.parentNode.removeChild(isntacontrol);
                        }
                    }
                }
            }else{
                e.removeAttribute('controls');
                e.controls = "";
            }
            if (jsf.loop){
                e.setAttribute('loop', '');
                e.loop = "loop";
            }else{
                e.removeAttribute('loop');
                e.loop = "";
            }
            if (jsf.muted && !play_click){
                e.setAttribute('muted', '');
                e.muted = "muted";
                first_load_mute = true;
            }else{
                e.removeAttribute('muted');
            }
            if (jsf.autoplay && !play_click){
                //console.log("autoplay");
                e.setAttribute('autoplay', '');
                e.autoplay = "autoplay";
                e.play();
            }else if (!play_click){
                e.removeAttribute('autoplay');
                e.autoplay = "";
                e.pause();
                //console.log("pause");
            }
        };
    }

    const createStyle = (function createStyleModule() {
        function createStyleElement(rules, opts) {
            const style = _createElement('style');
            Object.assign(style, opts.props);
            opts.root.appendChild(style);

            if (style.sheet) // style.sheet is only available when style attached to DOM
                rules.forEach(style.sheet.insertRule.bind(style.sheet));
            else
                style.textContent = rules.join('\n');

            if (opts.protect) {
                Object.defineProperty(style, 'sheet', {
                    value: null,
                    enumerable: true
                });
                Object.defineProperty(style, 'disabled', { //pretend to be disabled
                    enumerable: true,
                    set() {},
                    get() {
                        return true;
                    }
                });
                (new MutationObserver(
                    () => opts.root.removeChild(style)
                )).observe(style, {
                    childList: true
                });
            }

            return style;
        }

        // functions to parse object-based rulesets
        function parseRule(rec) {
            /* jshint validthis: true */
            return this.concat(rec[0], ' {\n', Object.entries(rec[1]).map(parseProperty, this + '\t').join('\n'), '\n', this, '}');
        }

        function parseProperty(rec) {
            /* jshint validthis: true */
            return rec[1] instanceof Object ? parseRule.call(this, rec) : `${this}${rec[0].replace(/_/g, '-')}: ${rec[1]};`;
        }

        // main
        const createStyle = (rules, opts) => {
            // parse options
            opts = Object.assign({
                protect: true,
                root: _de,
                type: 'text/css'
            }, opts);
            // move style properties into separate property
            // { a, b, ...rest } construction is not available in Fx 52
            opts.props = Object.assign({}, opts);
            delete opts.props.protect;
            delete opts.props.root;
            // store binded methods instead of element
            opts.root = {
                appendChild: opts.root.appendChild.bind(opts.root),
                removeChild: opts.root.removeChild.bind(opts.root)
            };

            // convert rules set into an array if it isn't one already
            rules = Array.isArray(rules) ? rules : rules instanceof Object ? Object.entries(rules).map(parseRule, '') : [rules];

            // could be reassigned when protection triggered
            let style = createStyleElement(rules, opts);

            if (!opts.protect)
                return style;

            const replaceStyle = () => new Promise(
                resolve => setTimeout(re => re(createStyleElement(rules, opts)), 0, resolve)
            ).then(st => (style = st)); // replace poiner to style object with a new style object

            (new MutationObserver(ms => {
                for (let m of ms)
                    for (let node of m.removedNodes)
                        if (node === style) replaceStyle();
            })).observe(_de, {
                childList: true
            });


            return style;
        };
        createStyle.toString = () => `const createStyle = (${createStyleModule.toString()})();`;
        return createStyle;
    })();

    const lines = {
        linked: [],
        MenuOptions: {
            eng: 'Options',
            rus: 'Настройки'
        },
        langs: {
            eng: 'English',
            rus: 'Русский'
        },
        HeaderName: {
            eng: 'HTML5 video settings',
            rus: 'Настройки HTML5 видео'
        },
        HeaderTools: {
            eng: 'Tools',
            rus: 'Инструменты'
        },
        HeaderOptions: {
            eng: 'Options',
            rus: 'Настройки'
        },
        controlsLabel: {
            eng: 'Show controls',
            rus: 'Отображать элементы управления'
        },
        loopLabel: {
            eng: 'Loop video',
            rus: 'Повтор видео'
        },
        autoplayLabel: {
            eng: 'Autoplay video',
            rus: 'Автоматическое воспроизведение видео'
        },
        autoplayTip: {
            eng: 'Autoplay may not working if "Mute sound" not enabled',
            rus: 'Автовоспроизведение может не работать, если не установлен режим \"отключить звук\"'
        },
        mutedLabel: {
            eng: 'Mute sound',
            rus: 'Отключить звук'
        },
        reg(el, name) {
            this[name].link = el;
            this.linked.push(name);
        },
        setLang(lang = 'eng') {
            for (let name of this.linked) {
                const el = this[name].link;
                const label = this[name][lang];
                el.textContent = label;
            }
            this.langs.link.value = lang;
            jsf.Lang = lang;
        }
    };

    const _createTextNode = _Document.createTextNode.bind(_document);
    const createOptionsWindow = () => {
        const root = _createElement('div'),
            shadow = _attachShadow ? _attachShadow(root, {
                mode: 'closed'
            }) : root,
            overlay = _createElement('div'),
            inner = _createElement('div');

        overlay.id = 'overlay';
        overlay.appendChild(inner);
        shadow.appendChild(overlay);

        inner.id = 'inner';
        inner.br = function appendBreakLine() {
            return this.appendChild(_createElement('br'));
        };

        createStyle({
            'h2': {
                margin_top: 0,
                white_space: 'nowrap'
            },
            'h2, h3': {
                margin_block_end: '0.5em'
            },
            'h4': {
                margin_block_start: '0em',
                margin_block_end: '0.5em',
                margin_left: '0.4em',
                font_family: 'Helvetica, Arial, sans-serif',
                font_size: '8pt',
                font_style: 'italic',
                font_weight: 'normal'
            },
            'div, button, select, input': {
                font_family: 'Helvetica, Arial, sans-serif',
                font_size: '12pt'
            },
            'select': {
                border: '1px solid darkgrey',
                border_radius: '0px 0px 5px 5px',
                border_top: '0px'
            },
            '#overlay': {
                position: 'fixed',
                top: 0,
                left: 0,
                bottom: 0,
                right: 0,
                background: 'rgba(0,0,0,0.65)',
                z_index: 2147483647 // Highest z-index: Math.pow(2, 31) - 1
            },
            '#inner': {
                background: 'whitesmoke',
                color: 'black',
                padding: '1.5em 1em 1.5em 1em',
                max_width: '150ch',
                position: 'absolute',
                top: '50%',
                left: '50%',
                transform: 'translate(-50%, -50%)',
                border: '1px solid darkgrey',
                border_radius: '5px'
            },
            '#closeOptionsButton': {
                float: 'right',
                transform: 'translate(1em, -1.5em)',
                border: 0,
                border_radius: 0,
                background: 'none',
                box_shadow: 'none'
            },
            '#selectLang': {
                float: 'right',
                transform: 'translate(0, -1.5em)'
            },
            '.optionsLabel': {
                padding_left: '1.5em',
                text_indent: '-1em',
                display: 'block'
            },
            '.optionsCheckbox': {
                left: '-0.25em',
                width: '1em',
                height: '1em',
                padding: 0,
                margin: 0,
                position: 'relative',
                vertical_align: 'middle'
            },
            '@media (prefers-color-scheme: dark)': {
                '#inner': {
                    background_color: '#292a2d',
                    color: 'white',
                    border: '1px solid #1a1b1e'
                },
                'input': {
                    filter: 'invert(100%)'
                },
                'select': {
                    background_color: '#303030',
                    color: '#f0f0f0',
                    border: '1px solid #1a1b1e',
                    border_radius: '0px 0px 5px 5px',
                    border_top: '0px'
                },
                '#overlay': {
                    background: 'rgba(0,0,0,.85)',
                }
            }
        }, {
            root: shadow,
            protect: false
        });

        // components
        function createCheckbox(name) {
            const checkbox = _createElement('input'),
                label = _createElement('label');
            checkbox.type = 'checkbox';
            checkbox.classList.add('optionsCheckbox');
            checkbox.checked = jsf[name];
            checkbox.id = name+'_checkbox';
            checkbox.onclick = e => {
                jsf[name] = e.target.checked;
                return true;
            };
            label.classList.add('optionsLabel');
            label.appendChild(checkbox);
            const text = _createTextNode('');
            label.appendChild(text);
            Object.defineProperty(label, 'textContent', {
                set(title) {
                    text.textContent = title;
                }
            });
            return label;
        }

        // language & close
        const closeBtn = _createElement('button');
        closeBtn.onclick = () => _removeChild(root);
        closeBtn.textContent = '\u2715';
        closeBtn.id = 'closeOptionsButton';
        inner.appendChild(closeBtn);

        overlay.addEventListener('click', e => {
            if (e.target === overlay) {
                _removeChild(root);
                e.preventDefault();
            }
            e.stopPropagation();
        }, false);

        const selectLang = _createElement('select');
        for (let name in lines.langs) {
            const langOption = _createElement('option');
            langOption.value = name;
            langOption.innerText = lines.langs[name];
            selectLang.appendChild(langOption);
        }
        selectLang.id = 'selectLang';
        lines.langs.link = selectLang;
        inner.appendChild(selectLang);

        selectLang.onchange = e => {
            const lang = e.target.value;
            lines.setLang(lang);
        };

        // fill options form

        lines.reg(inner.appendChild(_createElement('h2')), 'HeaderName');

        lines.reg(inner.appendChild(_createElement('h3')), 'HeaderOptions');

        lines.reg(inner.appendChild(createCheckbox('controls')), 'controlsLabel');
        lines.reg(inner.appendChild(createCheckbox('loop')), 'loopLabel');
        lines.reg(inner.appendChild(createCheckbox('autoplay')), 'autoplayLabel');

        lines.reg(inner.appendChild(_createElement('h4')), 'autoplayTip');

        lines.reg(inner.appendChild(createCheckbox('muted')), 'mutedLabel');

        lines.setLang(jsf.Lang);

        return root;
    };

    let optionsWindow;
    GM_registerMenuCommand(lines.MenuOptions[jsf.Lang], () => _appendChild(optionsWindow = optionsWindow || createOptionsWindow()));

    if( document.readyState !== 'loading' ) {
        setTimeout (function () {html5_video_set;}, startdelay);
    } else {
        document.addEventListener('DOMContentLoaded', function () {
            setTimeout (function () {html5_video_set;}, startdelay);
        });
    }
    function video_click(){
        play_click_iframe = true;
        //console.log("play_click_iframe = " + play_click_iframe);
        if(play_click_iframe) {
            clearTimeout(play_click_timeout);
            play_click_timeout = setTimeout(function () {play_click_switch(false);}, clickdelay);
            //setTimeout(function () {console.log("play_click_iframe = " + play_click_iframe);}, clickdelay+100);
        }
        //console.log("clicked");

    };
    document.addEventListener('play', function(e){

        document.addEventListener('click', video_click, true);
        setTimeout (function () {html5_video_set(play_click_iframe);}, playdelay);
        //console.log("play");
    }, true);

})();