HTML5 video settings

Change on load default HTML5 video behavior

目前为 2020-09-11 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name HTML5 video settings
  3. // @name:ru Настройки HTML5 видео
  4. // @namespace html5-video-settings
  5. // @author smut
  6. // @version 2020.09.11.1
  7. // @icon https://img.icons8.com/color/344/video.png
  8. // @description Change on load default HTML5 video behavior
  9. // @description:ru Изменение дефолтных параметров воспроизведения HTML5 видео
  10. // @grant GM_getValue
  11. // @grant GM_setValue
  12. // @grant GM_listValues
  13. // @grant GM_registerMenuCommand
  14. // @grant GM.cookie
  15. // @grant unsafeWindow
  16. // @grant window.close
  17. // @exclude
  18. // @match *://*/*
  19. // ==/UserScript==
  20.  
  21. (function() {
  22.  
  23. 'use strict';
  24.  
  25. const win = (unsafeWindow || window);
  26.  
  27. const
  28. _Document = Object.getPrototypeOf(HTMLDocument.prototype),
  29. _Element = Object.getPrototypeOf(HTMLElement.prototype);
  30. const
  31. _Node = Object.getPrototypeOf(_Element);
  32.  
  33. const
  34. isSafari =
  35. Object.prototype.toString.call(window.HTMLElement).indexOf('Constructor') > 0 ||
  36. (function (p) {
  37. return p.toString() === "[object SafariRemoteNotification]";
  38. })(!window.safari || window.safari.pushNotification),
  39. isFirefox = 'InstallTrigger' in win,
  40. inIFrame = (win.self !== win.top);
  41. const
  42. _bindCall = fun => Function.prototype.call.bind(fun),
  43. _getAttribute = _bindCall(_Element.getAttribute),
  44. _setAttribute = _bindCall(_Element.setAttribute),
  45. _removeAttribute = _bindCall(_Element.removeAttribute),
  46. _hasOwnProperty = _bindCall(Object.prototype.hasOwnProperty),
  47. _toString = _bindCall(Function.prototype.toString),
  48. _document = win.document,
  49. _de = _document.documentElement,
  50. _appendChild = _Document.appendChild.bind(_de),
  51. _removeChild = _Document.removeChild.bind(_de),
  52. _createElement = _Document.createElement.bind(_document),
  53. _querySelector = _Document.querySelector.bind(_document),
  54. _querySelectorAll = _Document.querySelectorAll.bind(_document),
  55. _attachShadow = ('attachShadow' in _Element) ? _bindCall(_Element.attachShadow) : null,
  56. _apply = Reflect.apply,
  57. _construct = Reflect.construct;
  58.  
  59. let skipLander = true;
  60. try {
  61. skipLander = !(isFirefox && 'StopIteration' in win);
  62. } catch (ignore) {}
  63.  
  64. const jsf = (function () {
  65. const opts = {};
  66. let getValue = (a, b) => b,
  67. setValue = () => null,
  68. listValues = () => [];
  69. try {
  70. [getValue, setValue, listValues] = [GM_getValue, GM_setValue, GM_listValues];
  71. } catch (ignore) {}
  72. // defaults
  73. opts.Lang = 'eng';
  74. opts.controls = true;
  75. opts.loop = false;
  76. opts.autoplay = false;
  77. opts.muted = false;
  78. // load actual values
  79. for (let name of listValues())
  80. opts[name] = getValue(name, opts[name]);
  81. const checkName = name => {
  82. if (!_hasOwnProperty(opts, name))
  83. throw new Error('Attempt to access missing option value.');
  84. return true;
  85. };
  86. return new Proxy(opts, {
  87. get(opts, name) {
  88. if (name === 'toString')
  89. return () => JSON.stringify(opts);
  90. if (checkName(name))
  91. return opts[name];
  92. },
  93. set(opts, name, value) {
  94. if (checkName(name)) {
  95. opts[name] = value;
  96. setValue(name, value);
  97. }
  98. return true;
  99. }
  100. });
  101. })();
  102.  
  103. if (isFirefox && _document.constructor.prototype.toString() === '[object ImageDocumentPrototype]')
  104. return;
  105.  
  106. if (!NodeList.prototype[Symbol.iterator])
  107. NodeList.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
  108. if (!HTMLCollection.prototype[Symbol.iterator])
  109. HTMLCollection.prototype[Symbol.iterator] = Array.prototype[Symbol.iterator];
  110.  
  111. if (GM.cookie === undefined)
  112. GM.cookie = {
  113. list: () => ({
  114. then: () => null
  115. })
  116. };
  117.  
  118. const
  119. batchLand = [],
  120. batchPrepend = new Set(),
  121. _APIString = `const win = window, isFirefox = ${isFirefox}, inIFrame = ${inIFrame}, _document = win.document, _de = _document.documentElement,
  122. _Document = Object.getPrototypeOf(HTMLDocument.prototype), _Element = Object.getPrototypeOf(HTMLElement.prototype), _Node = Object.getPrototypeOf(_Element),
  123. _appendChild = _Document.appendChild.bind(_de), _removeChild = _Document.removeChild.bind(_de), skipLander = ${skipLander},
  124. _createElement = _Document.createElement.bind(_document), _querySelector = _Document.querySelector.bind(_document),
  125. _querySelectorAll = _Document.querySelectorAll.bind(_document), _bindCall = fun => Function.prototype.call.bind(fun),
  126. _getAttribute = _bindCall(_Element.getAttribute), _setAttribute = _bindCall(_Element.setAttribute),
  127. _removeAttribute = _bindCall(_Element.removeAttribute), _hasOwnProperty = _bindCall(Object.prototype.hasOwnProperty),
  128. _toString = _bindCall(Function.prototype.toString), _apply = Reflect.apply, _construct = Reflect.construct;
  129. const GM = { info: { version: '0.0', scriptHandler: null }, cookie: { list: () => ({ then: () => null }) } };
  130. const jsf = ${jsf.toString()}`,
  131. landScript = (f, pre) => {
  132. const script = _createElement('script');
  133. script.textContent = `(()=>{${_APIString}${[...pre].join(';')};(${f.join(')();(')})();})();`;
  134. _appendChild(script);
  135. _removeChild(script);
  136. },
  137. startdelay = 2000,
  138. playdelay = 200;
  139.  
  140. let scriptLander = f => f();
  141. if (!skipLander) {
  142. scriptLander = (func, ...prepend) => {
  143. prepend.forEach(x => batchPrepend.add(x));
  144. batchLand.push(func);
  145. };
  146. _document.addEventListener(
  147. 'DOMContentLoaded', () => void(scriptLander = (f, ...prep) => landScript([f], prep)), false
  148. );
  149. }
  150.  
  151. function html5_video_set(play_click) {
  152. for (var e of document.getElementsByTagName('video')){
  153. if (jsf.controls){
  154. e.setAttribute('controls', '');
  155. e.controls = "controls";
  156. if (play_click){
  157. if (window.location.hostname === 'www.instagram.com'){
  158. var instaoverlay = document.querySelector('.PyenC');
  159. var isntacontrol = document.querySelector('.fXIG0');
  160. //console.log (instaoverlay);
  161. if(document.querySelector('.PyenC')){
  162. instaoverlay.parentNode.removeChild(instaoverlay);
  163. }
  164. if(document.querySelector('.fXIG0')){
  165. isntacontrol.parentNode.removeChild(isntacontrol);
  166. }
  167. }
  168. }
  169. }else{
  170. e.removeAttribute('controls');
  171. e.controls = "";
  172. }
  173. if (jsf.loop){
  174. e.setAttribute('loop', '');
  175. e.loop = "loop";
  176. }else{
  177. e.removeAttribute('loop');
  178. e.loop = "";
  179. }
  180. if (jsf.muted && !play_click){
  181. e.setAttribute('muted', '');
  182. e.muted = "muted";
  183. }else{
  184. e.removeAttribute('muted');
  185. }
  186. if (jsf.autoplay){
  187. e.setAttribute('autoplay', '');
  188. e.autoplay = "autoplay";
  189. e.play();
  190. }else if (!play_click){
  191. //console.log("first disable");
  192. e.removeAttribute('autoplay');
  193. e.autoplay = "";
  194. e.pause();
  195. }
  196. };
  197. }
  198.  
  199. const createStyle = (function createStyleModule() {
  200. function createStyleElement(rules, opts) {
  201. const style = _createElement('style');
  202. Object.assign(style, opts.props);
  203. opts.root.appendChild(style);
  204.  
  205. if (style.sheet) // style.sheet is only available when style attached to DOM
  206. rules.forEach(style.sheet.insertRule.bind(style.sheet));
  207. else
  208. style.textContent = rules.join('\n');
  209.  
  210. if (opts.protect) {
  211. Object.defineProperty(style, 'sheet', {
  212. value: null,
  213. enumerable: true
  214. });
  215. Object.defineProperty(style, 'disabled', { //pretend to be disabled
  216. enumerable: true,
  217. set() {},
  218. get() {
  219. return true;
  220. }
  221. });
  222. (new MutationObserver(
  223. () => opts.root.removeChild(style)
  224. )).observe(style, {
  225. childList: true
  226. });
  227. }
  228.  
  229. return style;
  230. }
  231.  
  232. // functions to parse object-based rulesets
  233. function parseRule(rec) {
  234. /* jshint validthis: true */
  235. return this.concat(rec[0], ' {\n', Object.entries(rec[1]).map(parseProperty, this + '\t').join('\n'), '\n', this, '}');
  236. }
  237.  
  238. function parseProperty(rec) {
  239. /* jshint validthis: true */
  240. return rec[1] instanceof Object ? parseRule.call(this, rec) : `${this}${rec[0].replace(/_/g, '-')}: ${rec[1]};`;
  241. }
  242.  
  243. // main
  244. const createStyle = (rules, opts) => {
  245. // parse options
  246. opts = Object.assign({
  247. protect: true,
  248. root: _de,
  249. type: 'text/css'
  250. }, opts);
  251. // move style properties into separate property
  252. // { a, b, ...rest } construction is not available in Fx 52
  253. opts.props = Object.assign({}, opts);
  254. delete opts.props.protect;
  255. delete opts.props.root;
  256. // store binded methods instead of element
  257. opts.root = {
  258. appendChild: opts.root.appendChild.bind(opts.root),
  259. removeChild: opts.root.removeChild.bind(opts.root)
  260. };
  261.  
  262. // convert rules set into an array if it isn't one already
  263. rules = Array.isArray(rules) ? rules : rules instanceof Object ? Object.entries(rules).map(parseRule, '') : [rules];
  264.  
  265. // could be reassigned when protection triggered
  266. let style = createStyleElement(rules, opts);
  267.  
  268. if (!opts.protect)
  269. return style;
  270.  
  271. const replaceStyle = () => new Promise(
  272. resolve => setTimeout(re => re(createStyleElement(rules, opts)), 0, resolve)
  273. ).then(st => (style = st)); // replace poiner to style object with a new style object
  274.  
  275. (new MutationObserver(ms => {
  276. for (let m of ms)
  277. for (let node of m.removedNodes)
  278. if (node === style) replaceStyle();
  279. })).observe(_de, {
  280. childList: true
  281. });
  282.  
  283.  
  284. return style;
  285. };
  286. createStyle.toString = () => `const createStyle = (${createStyleModule.toString()})();`;
  287. return createStyle;
  288. })();
  289.  
  290. const lines = {
  291. linked: [],
  292. MenuOptions: {
  293. eng: 'Options',
  294. rus: 'Настройки'
  295. },
  296. langs: {
  297. eng: 'English',
  298. rus: 'Русский'
  299. },
  300. HeaderName: {
  301. eng: 'HTML5 video settings',
  302. rus: 'Настройки HTML5 видео'
  303. },
  304. HeaderTools: {
  305. eng: 'Tools',
  306. rus: 'Инструменты'
  307. },
  308. HeaderOptions: {
  309. eng: 'Options',
  310. rus: 'Настройки'
  311. },
  312. controlsLabel: {
  313. eng: 'Show controls',
  314. rus: 'Отображать элементы управления'
  315. },
  316. loopLabel: {
  317. eng: 'Loop video',
  318. rus: 'Повтор видео'
  319. },
  320. autoplayLabel: {
  321. eng: 'Autoplay video',
  322. rus: 'Автоматическое воспроизведение видео'
  323. },
  324. autoplayTip: {
  325. eng: 'Autoplay may not working if "Mute sound" not enabled',
  326. rus: 'Автовоспроизведение может не работать, если не установлен режим \"отключить звук\"'
  327. },
  328. mutedLabel: {
  329. eng: 'Mute sound',
  330. rus: 'Отключить звук'
  331. },
  332. reg(el, name) {
  333. this[name].link = el;
  334. this.linked.push(name);
  335. },
  336. setLang(lang = 'eng') {
  337. for (let name of this.linked) {
  338. const el = this[name].link;
  339. const label = this[name][lang];
  340. el.textContent = label;
  341. }
  342. this.langs.link.value = lang;
  343. jsf.Lang = lang;
  344. }
  345. };
  346.  
  347. const _createTextNode = _Document.createTextNode.bind(_document);
  348. const createOptionsWindow = () => {
  349. const root = _createElement('div'),
  350. shadow = _attachShadow ? _attachShadow(root, {
  351. mode: 'closed'
  352. }) : root,
  353. overlay = _createElement('div'),
  354. inner = _createElement('div');
  355.  
  356. overlay.id = 'overlay';
  357. overlay.appendChild(inner);
  358. shadow.appendChild(overlay);
  359.  
  360. inner.id = 'inner';
  361. inner.br = function appendBreakLine() {
  362. return this.appendChild(_createElement('br'));
  363. };
  364.  
  365. createStyle({
  366. 'h2': {
  367. margin_top: 0,
  368. white_space: 'nowrap'
  369. },
  370. 'h2, h3': {
  371. margin_block_end: '0.5em'
  372. },
  373. 'h4': {
  374. margin_block_start: '0em',
  375. margin_block_end: '0.5em',
  376. margin_left: '0.4em',
  377. font_family: 'Helvetica, Arial, sans-serif',
  378. font_size: '8pt',
  379. font_style: 'italic',
  380. font_weight: 'normal'
  381. },
  382. 'div, button, select, input': {
  383. font_family: 'Helvetica, Arial, sans-serif',
  384. font_size: '12pt'
  385. },
  386. 'select': {
  387. border: '1px solid darkgrey',
  388. border_radius: '0px 0px 5px 5px',
  389. border_top: '0px'
  390. },
  391. '#overlay': {
  392. position: 'fixed',
  393. top: 0,
  394. left: 0,
  395. bottom: 0,
  396. right: 0,
  397. background: 'rgba(0,0,0,0.65)',
  398. z_index: 2147483647 // Highest z-index: Math.pow(2, 31) - 1
  399. },
  400. '#inner': {
  401. background: 'whitesmoke',
  402. color: 'black',
  403. padding: '1.5em 1em 1.5em 1em',
  404. max_width: '150ch',
  405. position: 'absolute',
  406. top: '50%',
  407. left: '50%',
  408. transform: 'translate(-50%, -50%)',
  409. border: '1px solid darkgrey',
  410. border_radius: '5px'
  411. },
  412. '#closeOptionsButton': {
  413. float: 'right',
  414. transform: 'translate(1em, -1.5em)',
  415. border: 0,
  416. border_radius: 0,
  417. background: 'none',
  418. box_shadow: 'none'
  419. },
  420. '#selectLang': {
  421. float: 'right',
  422. transform: 'translate(0, -1.5em)'
  423. },
  424. '.optionsLabel': {
  425. padding_left: '1.5em',
  426. text_indent: '-1em',
  427. display: 'block'
  428. },
  429. '.optionsCheckbox': {
  430. left: '-0.25em',
  431. width: '1em',
  432. height: '1em',
  433. padding: 0,
  434. margin: 0,
  435. position: 'relative',
  436. vertical_align: 'middle'
  437. },
  438. '@media (prefers-color-scheme: dark)': {
  439. '#inner': {
  440. background_color: '#292a2d',
  441. color: 'white',
  442. border: '1px solid #1a1b1e'
  443. },
  444. 'input': {
  445. filter: 'invert(100%)'
  446. },
  447. 'select': {
  448. background_color: '#303030',
  449. color: '#f0f0f0',
  450. border: '1px solid #1a1b1e',
  451. border_radius: '0px 0px 5px 5px',
  452. border_top: '0px'
  453. },
  454. '#overlay': {
  455. background: 'rgba(0,0,0,.85)',
  456. }
  457. }
  458. }, {
  459. root: shadow,
  460. protect: false
  461. });
  462.  
  463. // components
  464. function createCheckbox(name) {
  465. const checkbox = _createElement('input'),
  466. label = _createElement('label');
  467. checkbox.type = 'checkbox';
  468. checkbox.classList.add('optionsCheckbox');
  469. checkbox.checked = jsf[name];
  470. checkbox.id = name+'_checkbox';
  471. checkbox.onclick = e => {
  472. jsf[name] = e.target.checked;
  473. return true;
  474. };
  475. label.classList.add('optionsLabel');
  476. label.appendChild(checkbox);
  477. const text = _createTextNode('');
  478. label.appendChild(text);
  479. Object.defineProperty(label, 'textContent', {
  480. set(title) {
  481. text.textContent = title;
  482. }
  483. });
  484. return label;
  485. }
  486.  
  487. // language & close
  488. const closeBtn = _createElement('button');
  489. closeBtn.onclick = () => _removeChild(root);
  490. closeBtn.textContent = '\u2715';
  491. closeBtn.id = 'closeOptionsButton';
  492. inner.appendChild(closeBtn);
  493.  
  494. overlay.addEventListener('click', e => {
  495. if (e.target === overlay) {
  496. _removeChild(root);
  497. e.preventDefault();
  498. }
  499. e.stopPropagation();
  500. }, false);
  501.  
  502. const selectLang = _createElement('select');
  503. for (let name in lines.langs) {
  504. const langOption = _createElement('option');
  505. langOption.value = name;
  506. langOption.innerText = lines.langs[name];
  507. selectLang.appendChild(langOption);
  508. }
  509. selectLang.id = 'selectLang';
  510. lines.langs.link = selectLang;
  511. inner.appendChild(selectLang);
  512.  
  513. selectLang.onchange = e => {
  514. const lang = e.target.value;
  515. lines.setLang(lang);
  516. };
  517.  
  518. // fill options form
  519.  
  520. lines.reg(inner.appendChild(_createElement('h2')), 'HeaderName');
  521.  
  522. lines.reg(inner.appendChild(_createElement('h3')), 'HeaderOptions');
  523.  
  524. lines.reg(inner.appendChild(createCheckbox('controls')), 'controlsLabel');
  525. lines.reg(inner.appendChild(createCheckbox('loop')), 'loopLabel');
  526. lines.reg(inner.appendChild(createCheckbox('autoplay')), 'autoplayLabel');
  527.  
  528. lines.reg(inner.appendChild(_createElement('h4')), 'autoplayTip');
  529.  
  530. lines.reg(inner.appendChild(createCheckbox('muted')), 'mutedLabel');
  531.  
  532. lines.setLang(jsf.Lang);
  533.  
  534. return root;
  535. };
  536.  
  537. let optionsWindow;
  538. GM_registerMenuCommand(lines.MenuOptions[jsf.Lang], () => _appendChild(optionsWindow = optionsWindow || createOptionsWindow()));
  539.  
  540. if( document.readyState !== 'loading' ) {
  541. setTimeout (html5_video_set, startdelay);
  542. } else {
  543. document.addEventListener('DOMContentLoaded', function () {
  544. setTimeout (html5_video_set, startdelay);
  545. });
  546. }
  547. document.addEventListener('play', function(e){
  548. setTimeout (html5_video_set(true), playdelay);
  549. }, true);
  550.  
  551. })();