Изменение дефолтных параметров воспроизведения HTML5 видео
// ==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); })();