Custom Native HTML5 Player with Shortcuts

Custom html5 player with shortcuts and v.redd.it videos with audio

目前为 2022-01-11 提交的版本。查看 最新版本

  1. // ==UserScript==
  2. // @name Custom Native HTML5 Player with Shortcuts
  3. // @namespace https://gist.github.com/narcolepticinsomniac
  4. // @version 1.6
  5. // @description Custom html5 player with shortcuts and v.redd.it videos with audio
  6. // @author narcolepticinsomniac
  7. // @include *
  8. // @require https://cdnjs.cloudflare.com/ajax/libs/arrive/2.4.1/arrive.min.js
  9. // @run-at document-start
  10. // @grant GM.xmlHttpRequest
  11. // @connect v.redd.it
  12. // ==/UserScript==
  13.  
  14. let imagusAudio;
  15. let audioSync;
  16. let audioError;
  17. let ytID;
  18. let ytTimeChecked;
  19. const $ = document.querySelector.bind(document);
  20. const $$ = document.querySelectorAll.bind(document);
  21. const newEl = document.createElement.bind(document);
  22.  
  23. const settings = {
  24. // delay to hide contols and cursor if inactive (set to 3000 milliseconds)
  25. hideControls: 3000,
  26. // delay for fullscreen double-click (set to 300 milliseconds)
  27. clickDelay: 300,
  28. // right-click delay to match imagus user setting (set to 0 milliseconds)
  29. imagusStickyDelay: 0,
  30. // right/left arrows keys or inner skip buttons (set to 10 seconds)
  31. skipNormal: 10,
  32. // Shift + Arrow keys or outer skip buttons (set to 30 seconds)
  33. skipShift: 30,
  34. // Ctrl + Arrow keys skip (set to 1 minute)
  35. skipCtrl: 1,
  36. };
  37.  
  38. const shortcutFuncs = {
  39. toggleCaptions: v => {
  40. const validTracks = [];
  41. for (let i = 0; i < v.textTracks.length; ++i) {
  42. const tt = v.textTracks[i];
  43. if (tt.mode === 'showing') {
  44. tt.mode = 'disabled';
  45. if (v.textTracks.addEventListener) {
  46. // If text track event listeners are supported
  47. // (they are on the most recent Chrome), add
  48. // a marker to remember the old track. Use a
  49. // listener to delete it if a different track
  50. // is selected.
  51. v.cbhtml5vsLastCaptionTrack = tt.label;
  52.  
  53. function cleanup(e) {
  54. for (let i = 0; i < v.textTracks.length; ++i) {
  55. const ott = v.textTracks[i];
  56. if (ott.mode === 'showing') {
  57. delete v.cbhtml5vsLastCaptionTrack;
  58. v.textTracks.removeEventListener('change', cleanup);
  59. return;
  60. }
  61. }
  62. }
  63. v.textTracks.addEventListener('change', cleanup);
  64. }
  65. return;
  66. } else if (tt.mode !== 'hidden') {
  67. validTracks.push(tt);
  68. }
  69. }
  70. // If we got here, none of the tracks were selected.
  71. if (validTracks.length === 0) {
  72. return true; // Do not prevent default if no UI activated
  73. }
  74. // Find the best one and select it.
  75. validTracks.sort((a, b) => {
  76. if (v.cbhtml5vsLastCaptionTrack) {
  77. const lastLabel = v.cbhtml5vsLastCaptionTrack;
  78.  
  79. if (a.label === lastLabel && b.label !== lastLabel) {
  80. return -1;
  81. } else if (b.label === lastLabel && a.label !== lastLabel) {
  82. return 1;
  83. }
  84. }
  85.  
  86. const aLang = a.language.toLowerCase();
  87. const bLang = b.language.toLowerCase();
  88. const navLang = navigator.language.toLowerCase();
  89.  
  90. if (aLang === navLang && bLang !== navLang) {
  91. return -1;
  92. } else if (bLang === navLang && aLang !== navLang) {
  93. return 1;
  94. }
  95.  
  96. const aPre = aLang.split('-')[0];
  97. const bPre = bLang.split('-')[0];
  98. const navPre = navLang.split('-')[0];
  99.  
  100. if (aPre === navPre && bPre !== navPre) {
  101. return -1;
  102. } else if (bPre === navPre && aPre !== navPre) {
  103. return 1;
  104. }
  105.  
  106. return 0;
  107. })[0].mode = 'showing';
  108. },
  109.  
  110. togglePlay: v => {
  111. v.paused ? v.play() : v.pause();
  112. },
  113.  
  114. toStart: v => {
  115. v.currentTime = 0;
  116. },
  117.  
  118. toEnd: v => {
  119. v.currentTime = v.duration;
  120. },
  121.  
  122. skipLeft: (v, key, shift, ctrl) => {
  123. if (shift) {
  124. v.currentTime -= settings.skipShift;
  125. } else if (ctrl) {
  126. v.currentTime -= settings.skipCtrl;
  127. } else {
  128. v.currentTime -= settings.skipNormal;
  129. }
  130. },
  131.  
  132. skipRight: (v, key, shift, ctrl) => {
  133. if (shift) {
  134. v.currentTime += settings.skipShift;
  135. } else if (ctrl) {
  136. v.currentTime += settings.skipCtrl;
  137. } else {
  138. v.currentTime += settings.skipNormal;
  139. }
  140. },
  141.  
  142. increaseVol: v => {
  143. if (audioError) return;
  144. if (v.nextSibling.querySelector('volume.disabled')) {
  145. v.volume = 0;
  146. return;
  147. }
  148. const increase = (v.volume + 0.1).toFixed(1);
  149. if (v.muted) {
  150. v.muted = !v.muted;
  151. v.volume = 0.1;
  152. } else {
  153. v.volume <= 0.9 ? v.volume = increase : v.volume = 1;
  154. }
  155. },
  156.  
  157. decreaseVol: v => {
  158. if (audioError) return;
  159. if (v.nextSibling.querySelector('volume.disabled')) {
  160. v.volume = 0;
  161. return;
  162. }
  163. const decrease = (v.volume - 0.1).toFixed(1);
  164. v.volume >= 0.1 ? v.volume = decrease : v.volume = 0;
  165. },
  166.  
  167. toggleMute: v => {
  168. v.muted = !v.muted;
  169. if (audioSync) imagusAudio.muted = v.muted;
  170. },
  171.  
  172. toggleFS: v => {
  173. if (document.fullscreenElement) {
  174. document.exitFullscreen();
  175. v.parentElement.classList.remove('native-fullscreen');
  176. } else {
  177. v.parentElement.classList.add('native-fullscreen');
  178. v.parentElement.requestFullscreen(Element.ALLOW_KEYBOARD_INPUT);
  179. }
  180. },
  181.  
  182. reloadVideo: v => {
  183. const currTime = v.currentTime;
  184. v.load();
  185. v.currentTime = currTime;
  186. },
  187.  
  188. slowOrPrevFrame: (v, key, shift) => {
  189. if (shift) { // Less-Than
  190. v.currentTime -= 1 / 60;
  191. } else { // Comma
  192. if (v.playbackRate >= 0.1) {
  193. const decrease = (v.playbackRate - 0.1).toFixed(2);
  194. const rate = v.nextSibling.querySelector('rate');
  195. v.playbackRate = decrease;
  196. rate.textContent = `${v.playbackRate}x`;
  197. if (v.playbackRate !== 1) {
  198. rate.setAttribute('data-current-rate', `${v.playbackRate}x`);
  199. }
  200. if (v.playbackRate === 0.9) {
  201. v.classList.add('playback-rate-decreased');
  202. } else if (v.playbackRate === 1.1) {
  203. v.classList.add('playback-rate-increased');
  204. } else if (v.playbackRate === 1) {
  205. v.classList.remove('playback-rate-decreased');
  206. v.classList.remove('playback-rate-increased');
  207. rate.removeAttribute('data-current-rate');
  208. }
  209. } else {
  210. v.playbackRate = 0;
  211. }
  212. if (audioSync) imagusAudio.playbackRate = v.playbackRate;
  213. }
  214. },
  215.  
  216. fastOrNextFrame: (v, key, shift) => {
  217. if (shift) { // Greater-Than
  218. v.currentTime += 1 / 60;
  219. } else { // Period
  220. if (v.playbackRate <= 15.9) {
  221. const increase = (v.playbackRate += 0.1).toFixed(2);
  222. const rate = v.nextSibling.querySelector('rate');
  223. v.playbackRate = increase;
  224. rate.textContent = `${v.playbackRate}x`;
  225. if (v.playbackRate !== 1) {
  226. rate.setAttribute('data-current-rate', `${v.playbackRate}x`);
  227. }
  228. if (v.playbackRate === 0.9) {
  229. v.classList.add('playback-rate-decreased');
  230. } else if (v.playbackRate === 1.1) {
  231. v.classList.add('playback-rate-increased');
  232. } else if (v.playbackRate === 1) {
  233. v.classList.remove('playback-rate-decreased');
  234. v.classList.remove('playback-rate-increased');
  235. rate.removeAttribute('data-current-rate');
  236. }
  237. } else {
  238. v.playbackRate = 16;
  239. }
  240. if (audioSync) imagusAudio.playbackRate = v.playbackRate;
  241. }
  242. },
  243.  
  244. normalSpeed: v => { // ?
  245. v.playbackRate = v.defaultPlaybackRate;
  246. if (audioSync) imagusAudio.playbackRate = v.playbackRate;
  247. v.classList.remove('playback-rate-decreased');
  248. v.classList.remove('playback-rate-increased');
  249. v.nextSibling.querySelector('rate').textContent = '1x';
  250. v.nextSibling.querySelector('rate').removeAttribute('data-current-rate');
  251. },
  252.  
  253. toPercentage: (v, key) => {
  254. v.currentTime = (v.duration * (key - 48)) / 10.0;
  255. },
  256. };
  257.  
  258. const keyFuncs = {
  259. 32: shortcutFuncs.togglePlay, // Space
  260. 75: shortcutFuncs.togglePlay, // K
  261. 35: shortcutFuncs.toEnd, // End
  262. 48: shortcutFuncs.toStart, // 0
  263. 36: shortcutFuncs.toStart, // Home
  264. 37: shortcutFuncs.skipLeft, // Left arrow
  265. 74: shortcutFuncs.skipLeft, // J
  266. 39: shortcutFuncs.skipRight, // Right arrow
  267. 76: shortcutFuncs.skipRight, // L
  268. 38: shortcutFuncs.increaseVol, // Up arrow
  269. 40: shortcutFuncs.decreaseVol, // Down arrow
  270. 77: shortcutFuncs.toggleMute, // M
  271. 70: shortcutFuncs.toggleFS, // F
  272. 67: shortcutFuncs.toggleCaptions, // C
  273. 82: shortcutFuncs.reloadVideo, // R
  274. 188: shortcutFuncs.slowOrPrevFrame, // Comma or Less-Than
  275. 190: shortcutFuncs.fastOrNextFrame, // Period or Greater-Than
  276. 191: shortcutFuncs.normalSpeed, // Forward slash or ?
  277. 49: shortcutFuncs.toPercentage, // 1
  278. 50: shortcutFuncs.toPercentage, // 2
  279. 51: shortcutFuncs.toPercentage, // 3
  280. 52: shortcutFuncs.toPercentage, // 4
  281. 53: shortcutFuncs.toPercentage, // 5
  282. 54: shortcutFuncs.toPercentage, // 6
  283. 55: shortcutFuncs.toPercentage, // 7
  284. 56: shortcutFuncs.toPercentage, // 8
  285. 57: shortcutFuncs.toPercentage, // 9
  286. };
  287.  
  288. function customPlayer(v) {
  289. let videoWrapper;
  290. let savedTimeKey;
  291. let mouseDown;
  292. let isPlaying;
  293. let isSeeking;
  294. let earlyXposPercent;
  295. let preventMouseMove;
  296. let controlsTimeout;
  297. let imagusMouseTimeout;
  298. let imagusVid;
  299. let muteTillSync;
  300. let loaded;
  301. let error;
  302. let elToFocus;
  303. let target;
  304. let clickCount = 0;
  305. let repeat = 0;
  306. const directVideo = /video/.test(document.contentType) &&
  307. document.body.firstElementChild === v;
  308. const controls = newEl('controls');
  309. const imagus = v.classList.contains('imagus');
  310. if (imagus && !imagusVid) {
  311. imagusVid = v;
  312. imagusAudio = newEl('video');
  313. imagusAudio.preload = 'auto';
  314. imagusAudio.autoplay = 'true';
  315. imagusAudio.className = 'imagus imagus-audio';
  316. imagusAudio.style = 'display: none!important;';
  317. imagusVid.parentElement.insertBefore(imagusAudio, imagusVid);
  318. }
  319. if (directVideo) {
  320. elToFocus = document.body;
  321. self === top ? document.body.classList.add('direct-video-top-level') :
  322. document.body.classList.add('direct-video-embed');
  323. } else {
  324. elToFocus = v;
  325. videoWrapper = newEl('videowrapper');
  326. v.parentNode.insertBefore(videoWrapper, v);
  327. videoWrapper.appendChild(v);
  328. if (!imagus) {
  329. const compStyles = getComputedStyle(v);
  330. const position = compStyles.getPropertyValue('position');
  331. const zIndex = compStyles.getPropertyValue('z-index');
  332. if (position === 'absolute') {
  333. videoWrapper.style.setProperty('--wrapper-position', `${position}`);
  334. }
  335. if (zIndex !== 'auto') {
  336. controls.style.setProperty('--controls-z-index', `calc(${zIndex} + 1)`);
  337. }
  338. }
  339. }
  340. v.parentNode.insertBefore(controls, v.nextSibling);
  341. const playButton = newEl('btn');
  342. playButton.className = 'toggle-play';
  343. controls.appendChild(playButton);
  344. const beginButton = newEl('btn');
  345. beginButton.className = 'begin';
  346. controls.appendChild(beginButton);
  347. const skipLongLeft = newEl('btn');
  348. skipLongLeft.className = 'skip-long left';
  349. controls.appendChild(skipLongLeft);
  350. const skipShortLeft = newEl('btn');
  351. skipShortLeft.className = 'skip-short left';
  352. controls.appendChild(skipShortLeft);
  353. const skipShortRight = newEl('btn');
  354. skipShortRight.className = 'skip-short right';
  355. controls.appendChild(skipShortRight);
  356. const skipLongRight = newEl('btn');
  357. skipLongRight.className = 'skip-long right';
  358. controls.appendChild(skipLongRight);
  359. const timelineWrapper = newEl('timelinewrapper');
  360. controls.appendChild(timelineWrapper);
  361. const currentTime = newEl('currenttime');
  362. currentTime.textContent = '0:00';
  363. timelineWrapper.appendChild(currentTime);
  364. const timeline = newEl('timeline');
  365. timelineWrapper.appendChild(timeline);
  366. const timeBar = newEl('timebar');
  367. timeline.appendChild(timeBar);
  368. const timeBuffer = newEl('timebuffer');
  369. timeBar.appendChild(timeBuffer);
  370. const timeProgress = newEl('timeprogress');
  371. timeBar.appendChild(timeProgress);
  372. const timeSlider = newEl('input');
  373. timeSlider.type = 'range';
  374. timeSlider.value = 0;
  375. timeSlider.min = 0;
  376. timeSlider.max = 100;
  377. timeSlider.step = 0.01;
  378. timeSlider.textContent = '';
  379. timeline.appendChild(timeSlider);
  380. const timeTooltip = newEl('timetooltip');
  381. timeTooltip.className = 'hidden';
  382. timeline.appendChild(timeTooltip);
  383. const preview = newEl('preview');
  384. timeTooltip.appendChild(preview);
  385. const timeText = newEl('timetext');
  386. timeTooltip.appendChild(timeText);
  387. timeText.textContent = '-:-';
  388. const totalTime = newEl('totaltime');
  389. totalTime.textContent = '-:-';
  390. timelineWrapper.appendChild(totalTime);
  391. const rateDecrease = newEl('btn');
  392. rateDecrease.className = 'rate-decrease';
  393. controls.appendChild(rateDecrease);
  394. const rate = newEl('rate');
  395. rate.textContent = '1x';
  396. controls.appendChild(rate);
  397. const rateIncrease = newEl('btn');
  398. rateIncrease.className = 'rate-increase';
  399. controls.appendChild(rateIncrease);
  400. const volume = newEl('volume');
  401. controls.appendChild(volume);
  402. const volumeBar = newEl('volumebar');
  403. volume.appendChild(volumeBar);
  404. const volumeTrail = newEl('volumetrail');
  405. volumeBar.appendChild(volumeTrail);
  406. const volumeSlider = newEl('input');
  407. volumeSlider.type = 'range';
  408. volumeSlider.min = 0;
  409. volumeSlider.max = 1;
  410. volumeSlider.step = 0.01;
  411. volumeSlider.textContent = '';
  412. volume.appendChild(volumeSlider);
  413. const volumeTooltip = newEl('volumetooltip');
  414. volumeTooltip.className = 'hidden';
  415. volumeTooltip.textContent = '0%';
  416. volume.appendChild(volumeTooltip);
  417. const muteButton = newEl('btn');
  418. muteButton.className = 'mute';
  419. controls.appendChild(muteButton);
  420. const expandButton = newEl('btn');
  421. expandButton.className = 'expand';
  422. controls.appendChild(expandButton);
  423. v.classList.remove('custom-native-player-hidden');
  424. if (v.querySelector('source')) v.classList.add('contains-source');
  425. if (videoWrapper) enforcePosition();
  426. volumeValues();
  427.  
  428. v.onloadedmetadata = e => {
  429. loaded = true;
  430. shortcutFuncs.normalSpeed(v);
  431. savedTimeKey = `${location.pathname}${location.search}${v.duration}`;
  432. const savedTime = localStorage.getItem(savedTimeKey);
  433. if (timeSlider.value === '0') {
  434. if (savedTime) v.currentTime = savedTime;
  435. } else if (earlyXposPercent) {
  436. const time = (earlyXposPercent * v.duration) / 100;
  437. v.currentTime = time;
  438. }
  439. currentTime.textContent = formatTime(v.currentTime);
  440. totalTime.textContent = formatTime(v.duration);
  441. v.classList.remove('disabled');
  442. sliderValues(e);
  443. };
  444.  
  445. v.onloadeddata = () => {
  446. const imagusVreddit = /v(cf)?\.redd\.it/.test(v.src);
  447. const vHasAudio = hasAudio(v);
  448. if (!vHasAudio && !imagusVreddit) {
  449. v.classList.add('muted');
  450. volumeSlider.value = 0;
  451. muteButton.classList.add('disabled');
  452. volume.classList.add('disabled');
  453. } else if (vHasAudio && !imagusVreddit) {
  454. if (v.volume && !v.muted) v.classList.remove('muted');
  455. volumeValues();
  456. if (volume.classList.contains('disabled')) {
  457. muteButton.classList.remove('disabled');
  458. volume.classList.remove('disabled');
  459. }
  460. }
  461. elToFocus.focus({preventScroll: true});
  462. if (v.duration <= settings.skipNormal) {
  463. skipShortLeft.classList.add('disabled');
  464. skipShortRight.classList.add('disabled');
  465. } else {
  466. skipShortLeft.classList.remove('disabled');
  467. skipShortRight.classList.remove('disabled');
  468. }
  469. if (v.duration <= settings.skipShift) {
  470. skipLongLeft.classList.add('disabled');
  471. skipLongRight.classList.add('disabled');
  472. } else {
  473. skipLongLeft.classList.remove('disabled');
  474. skipLongRight.classList.remove('disabled');
  475. }
  476. if (v.paused) {
  477. v.classList.add('paused');
  478. if (videoWrapper) videoWrapper.classList.add('paused');
  479. }
  480. if (imagus) v.currentTime = 0;
  481. };
  482.  
  483. v.oncanplay = () => {
  484. v.oncanplay = null;
  485. if (!loaded) {
  486. v.load();
  487. console.log('Custom native player reloaded');
  488. }
  489. };
  490.  
  491. v.onseeked = () => {
  492. styleBuffer();
  493. setTimeout(styleBuffer, 500);
  494. };
  495.  
  496. v.onprogress = () => {
  497. styleBuffer();
  498. setTimeout(styleBuffer, 500);
  499. };
  500.  
  501. v.ontimeupdate = e => {
  502. if (v.readyState > 0) {
  503. if (v.duration > 0 && !mouseDown) {
  504. sliderValues(e);
  505. totalTime.textContent = formatTime(v.duration);
  506. if (!imagus && savedTimeKey) localStorage.setItem(savedTimeKey, v.currentTime)
  507. }
  508. }
  509. };
  510.  
  511. v.onvolumechange = e => {
  512. if (audioError) return;
  513. if (audioSync) imagusAudio.volume = v.volume;
  514. if (v.muted || !v.volume) {
  515. v.classList.add('muted');
  516. volumeSlider.value = 0;
  517. volumeTrail.style.width = '0';
  518. localStorage.setItem('videomuted', 'true');
  519. } else {
  520. v.classList.remove('muted');
  521. sliderValues(e);
  522. v.volume > 0.1 ? localStorage.setItem('videovolume', v.volume) :
  523. localStorage.setItem('videovolume', 0.1);
  524. localStorage.setItem('videomuted', 'false');
  525. }
  526. };
  527.  
  528. v.onplay = () => {
  529. if (v === imagusVid && audioSync) imagusAudio.play();
  530. v.classList.remove('paused');
  531. if (videoWrapper) videoWrapper.classList.remove('paused');
  532. v.classList.add('playing');
  533. };
  534.  
  535. v.onpause = () => {
  536. if (v === imagusVid && audioSync) imagusAudio.pause();
  537. if (!isSeeking) {
  538. v.classList.remove('playing');
  539. v.classList.add('paused');
  540. if (videoWrapper) videoWrapper.classList.add('paused');
  541. }
  542. };
  543.  
  544. v.onended = () => {
  545. if (localStorage.getItem(savedTimeKey)) localStorage.removeItem(savedTimeKey);
  546. savedTimeKey = false;
  547. };
  548.  
  549. v.onemptied = () => {
  550. if (v === imagusVid) {
  551. const vPP = v.parentNode.parentNode;
  552. if (imagusAudio.getAttribute('src') &&
  553. /v(cf)?\.redd\.it/.test(imagusAudio.src)) {
  554. audioSync = false;
  555. audioError = false;
  556. imagusAudio.pause();
  557. imagusAudio.removeAttribute('src');
  558. imagusAudio.load();
  559. imagusAudio.removeAttribute('loop');
  560. }
  561. if (v.src !== '') {
  562. if (/\.(mp3|m4a)/.test(v.src)) {
  563. vPP.classList.add('audio-only');
  564. }
  565. if (/v(cf)?\.redd\.it/.test(v.src)) {
  566. const prefix = v.src.split('DASH')[0].replace('vcf.', 'v.');
  567. const audioSrc = `${prefix}DASH_audio.mp4`;
  568. GM.xmlHttpRequest({
  569. method: 'GET',
  570. url: audioSrc,
  571. onload: xhr => {
  572. imagusAudio.src = xhr.status >= 200 && xhr.status < 300 ? audioSrc : `${prefix}audio`;
  573. },
  574. onerror: () => {
  575. imagusAudio.src = `${prefix}audio`;
  576. },
  577. });
  578. if (!imagusAudio.muted) {
  579. muteTillSync = true;
  580. imagusAudio.muted = true;
  581. }
  582. if (imagusVid.hasAttribute('loop')) imagusAudio.setAttribute('loop', 'true');
  583. }
  584. vPP.classList.add('imagus-video-wrapper');
  585. window.addEventListener('click', imagusClick, true);
  586. document.addEventListener('keyup', imagusKeys, true);
  587. document.addEventListener('mousedown', imagusMouseDown, true);
  588. document.addEventListener('mouseup', imagusMouseUp, true);
  589. } else {
  590. audioSync = false;
  591. audioError = false;
  592. imagusAudio.pause();
  593. imagusAudio.removeAttribute('src');
  594. imagusAudio.load();
  595. imagusAudio.removeAttribute('loop');
  596. vPP.removeAttribute('class');
  597. timeTooltip.classList.add('hidden');
  598. window.removeEventListener('click', imagusClick, true);
  599. document.removeEventListener('keyup', imagusKeys, true);
  600. document.removeEventListener('mousedown', imagusMouseDown, true);
  601. document.removeEventListener('mouseup', imagusMouseUp, true);
  602. }
  603. }
  604. };
  605.  
  606. v.onerror = () => {
  607. error = true;
  608. elToFocus.blur();
  609. v.classList.add('disabled');
  610. };
  611.  
  612. v.onmousedown = e => {
  613. if (error && e.button !== 2) return;
  614. e.stopPropagation();
  615. e.stopImmediatePropagation();
  616. if (e.button === 0) {
  617. clickCount++;
  618. const checkState = v.paused;
  619. if (clickCount === 1) {
  620. setTimeout(() => {
  621. if (clickCount === 1) {
  622. // avoid conflicts with existing click listeners
  623. const recheckState = v.paused;
  624. if (checkState === recheckState) shortcutFuncs.togglePlay(v);
  625. } else {
  626. shortcutFuncs.toggleFS(v);
  627. }
  628. clickCount = 0;
  629. }, settings.clickDelay);
  630. }
  631. } else if (e.button === 2) {
  632. window.addEventListener('contextmenu', preventHijack, true);
  633. }
  634. };
  635.  
  636. v.onmouseup = e => {
  637. if (e.button === 2) {
  638. setTimeout(() => {
  639. window.removeEventListener('contextmenu', preventHijack, true);
  640. }, 100);
  641. }
  642. if (error) elToFocus.blur();
  643. };
  644.  
  645. v.onmousemove = () => {
  646. controlsTimeout ? clearTimeout(controlsTimeout) :
  647. v.classList.add('active');
  648. if (videoWrapper) videoWrapper.classList.add('active');
  649. controlsTimeout = setTimeout(() => {
  650. controlsTimeout = false;
  651. v.classList.remove('active');
  652. if (videoWrapper) videoWrapper.classList.remove('active');
  653. }, settings.hideControls);
  654. };
  655.  
  656. new ResizeObserver(compactControls).observe(v);
  657.  
  658. controls.onmousedown = e => {
  659. if (e.button > 0) return;
  660. target = e.target;
  661. };
  662.  
  663. controls.onmouseup = () => {
  664. if (error) return;
  665. elToFocus.focus({preventScroll: true});
  666. target = null;
  667. };
  668.  
  669. timeSlider.onmousemove = e => sliderValues(e);
  670.  
  671. timeSlider.oninput = e => sliderValues(e);
  672.  
  673. timeSlider.onmousedown = e => {
  674. if (e.button > 0) return;
  675. mouseDown = true;
  676. isSeeking = true;
  677. if (timeTooltip.classList.contains('hidden')) sliderValues(e);
  678. if (v.readyState > 0) {
  679. if (!v.paused) {
  680. isPlaying = true;
  681. v.pause();
  682. } else {
  683. isPlaying = false;
  684. }
  685. }
  686. };
  687.  
  688. timeSlider.onmouseup = e => {
  689. if (e.button > 0 || e.target !== target) return;
  690. mouseDown = false;
  691. isSeeking = false;
  692. if (v.readyState > 0) {
  693. sliderValues(e);
  694. if (isPlaying) {
  695. v.play();
  696. isPlaying = false;
  697. }
  698. }
  699. };
  700.  
  701. volumeSlider.onmousemove = e => sliderValues(e);
  702.  
  703. volumeSlider.oninput = e => {
  704. if (v.muted) shortcutFuncs.toggleMute(v);
  705. sliderValues(e);
  706. };
  707.  
  708. muteButton.onmouseup = e => {
  709. if (e.button > 0 || e.target !== target) return;
  710. const lastVolume = localStorage.getItem('videovolume');
  711. if (v.muted || v.volume) shortcutFuncs.toggleMute(v);
  712. v.volume = lastVolume;
  713. if (audioSync) imagusAudio.muted = v.muted;
  714. };
  715.  
  716. playButton.onmouseup = e => {
  717. if (e.button > 0 || e.target !== target) return;
  718. shortcutFuncs.togglePlay(v);
  719. };
  720.  
  721. skipShortLeft.onmouseup = e => {
  722. if (e.button > 0 || e.target !== target) return;
  723. shortcutFuncs.skipLeft(v);
  724. };
  725.  
  726. skipShortRight.onmouseup = e => {
  727. if (e.button > 0 || e.target !== target) return;
  728. shortcutFuncs.skipRight(v);
  729. };
  730.  
  731. skipLongLeft.onmouseup = e => {
  732. if (e.button > 0 || e.target !== target) return;
  733. v.currentTime -= settings.skipShift;
  734. };
  735.  
  736. skipLongRight.onmouseup = e => {
  737. if (e.button > 0 || e.target !== target) return;
  738. v.currentTime += settings.skipShift;
  739. };
  740.  
  741. beginButton.onmouseup = e => {
  742. if (e.button > 0 || e.target !== target) return;
  743. v.currentTime = 0;
  744. timeSlider.value = 0;
  745. timeProgress.style.width = '0';
  746. currentTime.textContent = '0:00';
  747. };
  748.  
  749. rateDecrease.onmouseup = e => {
  750. if (e.button > 0 || e.target !== target) return;
  751. shortcutFuncs.slowOrPrevFrame(v);
  752. };
  753.  
  754. rateIncrease.onmouseup = e => {
  755. if (e.button > 0 || e.target !== target) return;
  756. shortcutFuncs.fastOrNextFrame(v);
  757. };
  758.  
  759. rate.onmouseup = e => {
  760. if (e.button > 0 || e.target !== target) return;
  761. shortcutFuncs.normalSpeed(v);
  762. };
  763.  
  764. rate.onmouseenter = () => {
  765. rate.textContent = '1x?';
  766. };
  767.  
  768. rate.onmouseleave = () => {
  769. const currentRate = rate.getAttribute('data-current-rate');
  770. if (currentRate) rate.textContent = currentRate;
  771. };
  772.  
  773. expandButton.onmouseup = e => {
  774. if (e.button > 0 || e.target !== target) return;
  775. shortcutFuncs.toggleFS(v);
  776. };
  777.  
  778. // exiting fullscreen by escape key or other browser provided method
  779. document.onfullscreenchange = () => {
  780. if (!document.fullscreenElement) {
  781. const nativeFS = $('.native-fullscreen');
  782. if (nativeFS) nativeFS.classList.remove('native-fullscreen');
  783. }
  784. };
  785.  
  786. if (imagusVid) {
  787. imagusAudio.onloadedmetadata = () => {
  788. audioSync = true;
  789. if (v.hasAttribute('autoplay')) imagusAudio.play();
  790. };
  791.  
  792. imagusAudio.onloadeddata = () => {
  793. if (v.volume && !v.muted) v.classList.remove('muted');
  794. volumeValues(v);
  795. if (volume.classList.contains('disabled')) {
  796. muteButton.classList.remove('disabled');
  797. volume.classList.remove('disabled');
  798. }
  799. };
  800.  
  801. imagusAudio.onended = () => {
  802. imagusAudio.currentTime = 0;
  803. if (imagusVid.hasAttribute('loop')) imagusAudio.play();
  804. };
  805.  
  806. imagusAudio.onerror = () => {
  807. audioError = true;
  808. v.classList.add('muted');
  809. volumeSlider.value = 0;
  810. muteButton.classList.add('disabled');
  811. volume.classList.add('disabled');
  812. };
  813. }
  814.  
  815. if (directVideo) {
  816. v.removeAttribute('tabindex');
  817. document.body.setAttribute('tabindex', '0');
  818. document.addEventListener('keydown', docHandleKeyDown, true);
  819. document.addEventListener('keypress', docHandleKeyOther, true);
  820. document.addEventListener('keyup', docHandleKeyOther, true);
  821. } else {
  822. v.addEventListener('keydown', handleKeyDown, false);
  823. v.addEventListener('keypress', handleKeyOther, false);
  824. v.addEventListener('keyup', handleKeyOther, false);
  825. }
  826.  
  827. function sliderValues(e) {
  828. let slider;
  829. let xPosition;
  830. const vid = audioSync ? imagusAudio && v : v;
  831. const eType = e && e.type;
  832. const eTime = eType === 'timeupdate';
  833. const eVolume = eType === 'volumechange';
  834. const eMeta = eType === 'loadedmetadata';
  835. const eData = eType === 'loadeddata';
  836. const eInput = eType === 'input';
  837. const eMouseUp = eType === 'mouseup';
  838. const eMouseMove = eType === 'mousemove';
  839. const eMouseDown = eType === 'mousedown';
  840. if (eMeta || eTime || eVolume || eData || !e) {
  841. slider = eMeta || eTime ? timeSlider : volumeSlider;
  842. } else {
  843. slider = e.target;
  844. }
  845. const tooltip = slider.nextSibling;
  846. const timeTarget = slider === timeSlider;
  847. const sliderWidth = slider.clientWidth;
  848. const halfSlider = sliderWidth / 2;
  849. const slider14ths = halfSlider / 7;
  850. const eX = e && e.offsetX;
  851. const start7 = eX <= 7;
  852. const end7 = eX >= sliderWidth - 7;
  853. if (eMouseMove || eMouseDown) {
  854. if (start7 || end7) {
  855. xPosition = start7 ? 0 : sliderWidth;
  856. } else {
  857. xPosition = eX < halfSlider ? (eX + (-7 + (eX / slider14ths))).toFixed(1) :
  858. (eX + ((eX - halfSlider) / slider14ths)).toFixed(1);
  859. }
  860. }
  861. if (eMeta || eTime || eVolume || eData || !e) {
  862. xPosition = eMeta || eTime ?
  863. ((((100 / v.duration) * v.currentTime) * sliderWidth) / 100).toFixed(1) :
  864. (v.volume * sliderWidth).toFixed(1);
  865. }
  866. if (eTime && e.target === imagusVid && audioSync) {
  867. if (imagusVid.currentTime - imagusAudio.currentTime >= 0.1 ||
  868. imagusVid.currentTime - imagusAudio.currentTime <= -0.1) {
  869. imagusAudio.currentTime = imagusVid.currentTime + 0.06;
  870. console.log('time sync corrected');
  871. if (muteTillSync && imagusAudio.readyState > 2) {
  872. imagusAudio.muted = false;
  873. muteTillSync = false;
  874. console.log('unmuted after time correct');
  875. }
  876. } else if (muteTillSync && imagusAudio.readyState > 2) {
  877. imagusAudio.muted = false;
  878. muteTillSync = false;
  879. console.log('unmuted');
  880. }
  881. }
  882. if (eInput || eMouseUp) xPosition = +tooltip.getAttribute('data-x-position');
  883. const xPosPercent = timeTarget ? (xPosition / sliderWidth) * 100 :
  884. Math.round((xPosition / sliderWidth) * 100);
  885. let time = (xPosPercent * v.duration) / 100;
  886. if (eInput || eMeta || eTime || eVolume || eData || !e) {
  887. const valueTrail = timeTarget ? timeProgress : volumeTrail;
  888. const offset = halfSlider < xPosition ? -7 + (xPosition / slider14ths) :
  889. (xPosition - halfSlider) / slider14ths;
  890. slider.value = timeTarget ? xPosPercent : xPosPercent / 100;
  891. valueTrail.style.width = `calc(${xPosPercent}% - ${offset}px)`;
  892. if (eInput && !timeTarget) {
  893. if (start7 || end7) {
  894. vid.volume = start7 ? 0 : 1;
  895. } else {
  896. vid.volume = xPosPercent / 100;
  897. }
  898. }
  899. if (eInput && timeTarget && v.readyState > 0) currentTime.textContent = formatTime(time);
  900. if (eTime) currentTime.textContent = formatTime(v.currentTime);
  901. if (eInput && timeTarget && v.readyState < 1) earlyXposPercent = xPosPercent;
  902. if (eMeta && !tooltip.classList.contains('hidden')) {
  903. xPosition = +tooltip.getAttribute('data-x-position');
  904. time = (xPosition / sliderWidth) * v.duration;
  905. timeText.textContent = formatTime(time);
  906. }
  907. } else if (eMouseUp) {
  908. if (audioSync) {
  909. if (start7 || end7) {
  910. imagusAudio.currentTime = start7 ? 0 : v.duration;
  911. } else {
  912. imagusAudio.currentTime = time;
  913. }
  914. }
  915. if (start7 || end7) {
  916. v.currentTime = start7 ? 0 : v.duration;
  917. } else {
  918. v.currentTime = time;
  919. }
  920. preventMouseMove = true;
  921. setTimeout(() => {
  922. preventMouseMove = false;
  923. }, 10);
  924. } else if (eMouseMove || eMouseDown) {
  925. if (!preventMouseMove || eMouseDown) {
  926. tooltip.dataset.xPosition = xPosition;
  927. tooltip.dataset.targetTime = time;
  928. tooltip.style.left = `${eX}px`;
  929. if (v.readyState > 0 && timeTarget) timeText.textContent = formatTime(time);
  930. if (!timeTarget) tooltip.textContent = `${xPosPercent}%`;
  931. }
  932. tooltip.classList.remove('hidden');
  933. preventMouseMove = false;
  934. }
  935. }
  936.  
  937. function styleBuffer() {
  938. if (v.readyState > 1 && v.duration > 0) {
  939. const buffer = (v.buffered.end(v.buffered.length - 1) / v.duration) * 100;
  940. timeBuffer.style.width = `${buffer}%`;
  941. }
  942. }
  943.  
  944. function formatTime(t) {
  945. if (isNaN(t)) {
  946. console.log(t)
  947. timeTooltip.classList.add('hidden');
  948. return '-:-';
  949. } else {
  950. let seconds = Math.round(t);
  951. const minutes = Math.floor(seconds / 60);
  952. if (minutes > 0) seconds -= minutes * 60;
  953. if (seconds.toString().length === 1) seconds = `0${seconds}`;
  954. return `${minutes}:${seconds}`;
  955. }
  956. }
  957.  
  958. function volumeValues() {
  959. const videovolume = localStorage.getItem('videovolume');
  960. const videomuted = localStorage.getItem('videomuted');
  961. if ((!videovolume && !videomuted) ||
  962. (videovolume && videovolume === '1' &&
  963. videomuted && videomuted !== 'true')) {
  964. v.volume = 1;
  965. volumeSlider.value = 1;
  966. volumeTrail.style.width = '100%';
  967. localStorage.setItem('videovolume', v.volume);
  968. localStorage.setItem('videomuted', 'false');
  969. } else if (videomuted && videomuted === 'true') {
  970. v.classList.add('muted');
  971. volumeSlider.value = 0;
  972. volumeTrail.style.width = '0';
  973. v.muted = true;
  974. } else {
  975. v.volume = videovolume;
  976. if (audioSync) imagusAudio.volume = v.volume;
  977. sliderValues();
  978. if (!volumeSlider.clientWidth) {
  979. new MutationObserver((_, observer) => {
  980. const volumeWidthSet = v.parentElement.querySelector('volume input').clientWidth;
  981. if (volumeWidthSet) {
  982. sliderValues();
  983. observer.disconnect();
  984. }
  985. }).observe(v.parentElement, {childList: true, subtree: true, attributes: true});
  986. }
  987. }
  988. }
  989.  
  990. function hasAudio() {
  991. return v.mozHasAudio ||
  992. Boolean(v.webkitAudioDecodedByteCount) ||
  993. Boolean(v.audioTracks && v.audioTracks.length);
  994. }
  995.  
  996. function compactControls() {
  997. const width = v.clientWidth;
  998. if (!width) return;
  999. if (width < 892) {
  1000. v.classList.add('compact');
  1001. if (imagus) v.parentNode.parentNode.classList.add('compact');
  1002. } else {
  1003. v.classList.remove('compact');
  1004. if (imagus) v.parentNode.parentNode.classList.remove('compact');
  1005. }
  1006. width < 412 ? v.classList.add('compact-2') : v.classList.remove('compact-2');
  1007. width < 316 ? v.classList.add('compact-3') : v.classList.remove('compact-3');
  1008. width < 246 ? v.classList.add('compact-4') : v.classList.remove('compact-4');
  1009. }
  1010.  
  1011. function imagusMouseDown(e) {
  1012. const vid = $('.imagus-video-wrapper');
  1013. if (vid && e.button === 2) {
  1014. e.stopImmediatePropagation();
  1015. imagusMouseTimeout = setTimeout(() => {
  1016. imagusMouseTimeout = 'sticky';
  1017. }, settings.imagusStickyDelay);
  1018. }
  1019. }
  1020.  
  1021. function imagusMouseUp(e) {
  1022. const vid = $('.imagus-video-wrapper');
  1023. if (vid && e.button === 2) {
  1024. if (imagusMouseTimeout === 'sticky') {
  1025. vid.classList.add('stickied');
  1026. setTimeout(() => {
  1027. v.removeAttribute('controls');
  1028. });
  1029. if (volume.classList.contains('disabled')) volumeSlider.value = 0;
  1030. document.removeEventListener('mousedown', imagusMouseDown, true);
  1031. document.removeEventListener('mouseup', imagusMouseUp, true);
  1032. } else {
  1033. clearInterval(imagusMouseTimeout);
  1034. imagusMouseTimeout = false;
  1035. }
  1036. }
  1037. }
  1038.  
  1039. function imagusClick(e) {
  1040. const imagusStickied = $('.imagus-video-wrapper.stickied');
  1041. if (imagusStickied) {
  1042. if (e.target.closest('.imagus-video-wrapper.stickied')) {
  1043. e.stopImmediatePropagation();
  1044. } else {
  1045. imagusStickied.removeAttribute('class');
  1046. e.preventDefault();
  1047. }
  1048. }
  1049. }
  1050.  
  1051. function imagusKeys(e) {
  1052. const vid = $('.imagus-video-wrapper');
  1053. if (vid) {
  1054. if (e.keyCode === 13 || e.keyCode === 90) {
  1055. vid.classList.add('stickied');
  1056. setTimeout(() => {
  1057. v.removeAttribute('controls');
  1058. });
  1059. if (volume.classList.contains('disabled')) volumeSlider.value = 0;
  1060. document.removeEventListener('keyup', imagusKeys, true);
  1061. document.removeEventListener('mousedown', imagusMouseDown, true);
  1062. document.removeEventListener('mouseup', imagusMouseUp, true);
  1063. }
  1064. }
  1065. }
  1066.  
  1067. function handleKeyDown(e) {
  1068. if (e.altKey || e.metaKey) return true; // Do not activate
  1069. const func = keyFuncs[e.keyCode];
  1070. if (func) {
  1071. if ((func.length < 3 && e.shiftKey) ||
  1072. (func.length < 4 && e.ctrlKey)) return true; // Do not activate
  1073. func(e.target, e.keyCode, e.shiftKey, e.ctrlKey);
  1074. e.preventDefault();
  1075. e.stopPropagation();
  1076. e.stopImmediatePropagation();
  1077. return false;
  1078. }
  1079. }
  1080.  
  1081. function handleKeyOther(e) {
  1082. if (e.altKey || e.metaKey) return true; // Do not prevent default
  1083. const func = keyFuncs[e.keyCode];
  1084. if (func) {
  1085. if ((func.length < 3 && e.shiftKey) ||
  1086. (func.length < 4 && e.ctrlKey)) return true; // Do not prevent default
  1087. e.preventDefault();
  1088. e.stopPropagation();
  1089. e.stopImmediatePropagation();
  1090. return false;
  1091. }
  1092. }
  1093.  
  1094. function docHandleKeyDown(e) {
  1095. if (document.body !== document.activeElement ||
  1096. e.altKey || e.metaKey) return true; // Do not activate
  1097. const func = keyFuncs[e.keyCode];
  1098. if (func) {
  1099. if ((func.length < 3 && e.shiftKey) ||
  1100. (func.length < 4 && e.ctrlKey)) return true; // Do not activate
  1101. func(v, e.keyCode, e.shiftKey, e.ctrlKey);
  1102. e.preventDefault();
  1103. e.stopPropagation();
  1104. return false;
  1105. }
  1106. }
  1107.  
  1108. function docHandleKeyOther(e) {
  1109. if (document.body !== document.activeElement ||
  1110. e.altKey || e.metaKey) return true; // Do not prevent default
  1111. const func = keyFuncs[e.keyCode];
  1112. if (func) {
  1113. if ((func.length < 3 && e.shiftKey) ||
  1114. (func.length < 4 && e.ctrlKey)) return true; // Do not prevent default
  1115. e.preventDefault();
  1116. e.stopPropagation();
  1117. return false;
  1118. }
  1119. }
  1120.  
  1121. // circumvent any scripts attempting to hijack video context menus
  1122. function preventHijack(e) {
  1123. e.stopPropagation();
  1124. e.stopImmediatePropagation();
  1125. const redirectEvent = e.target.ownerDocument.createEvent('MouseEvents');
  1126. redirectEvent.initMouseEvent(e, e.bubbles, e.cancelable);
  1127. return e;
  1128. }
  1129.  
  1130. function enforcePosition() {
  1131. setTimeout(() => {
  1132. let controlsDisplaced = controls !== v.nextSibling;
  1133. const vidDisplaced = videoWrapper !== v.parentNode;
  1134. if (vidDisplaced || controlsDisplaced) {
  1135. if (vidDisplaced) videoWrapper.appendChild(v);
  1136. controlsDisplaced = v !== controls.previousSibling;
  1137. if (controlsDisplaced) videoWrapper.insertBefore(controls, v.nextSibling);
  1138. const bs =
  1139. videoWrapper.querySelectorAll('videowrapper > *:not(video):not(controls)');
  1140. for (let i = 0; i < bs.length; ++i) {
  1141. bs[i].remove();
  1142. }
  1143. }
  1144. repeat++;
  1145. if (repeat < 10) enforcePosition.call(this);
  1146. }, 100);
  1147. }
  1148. }
  1149.  
  1150. function ytSaveCurrentTime(v) {
  1151. v.addEventListener('loadstart', ytCheckSavedTime);
  1152. v.addEventListener('loadeddata', ytCheckSavedTime);
  1153.  
  1154. v.ontimeupdate = () => {
  1155. if (v.currentTime > 0 && ytTimeChecked) localStorage.setItem(ytID, v.currentTime);
  1156. };
  1157.  
  1158. v.onended = () => {
  1159. if (localStorage.getItem(ytID)) localStorage.removeItem(ytID);
  1160. };
  1161. }
  1162.  
  1163. function ytCheckSavedTime(e) {
  1164. ytID = location.href.replace(/.*?\/(watch\?v=|embed\/)(.*?)(\?|&|$).*/, '$2');
  1165. const savedTime = localStorage.getItem(ytID);
  1166. const timeURL = /(\?|&)(t(ime_continue)?|start)=[1-9]/.test(location.href);
  1167. const ytStart = $('.ytp-clip-start:not([style*="left: 0%;"])');
  1168. if (e.type === 'loadstart') {
  1169. ytTimeChecked = false;
  1170. if ((!ytStart || !savedTime) && !timeURL) ytTimeChecked = true;
  1171. if (ytStart) ytStart.click();
  1172. if (ytTimeChecked && savedTime) e.target.currentTime = savedTime;
  1173. e.target.focus({preventScroll: true});
  1174. if (self === top) window.scroll({top: 0, behavior: 'smooth'});
  1175. } else if (e.type === 'loadeddata' && !ytTimeChecked) {
  1176. if (savedTime) e.target.currentTime = savedTime;
  1177. ytTimeChecked = true;
  1178. }
  1179. }
  1180.  
  1181. function moveStyle() {
  1182. const style = $('#custom-native-player-style');
  1183. if (!style || $('body ~ #custom-native-player-style')) return;
  1184. const docEls = $$('html > *');
  1185. for (let i = 0; i < docEls.length; i++) {
  1186. if (docEls[i].tagName === 'STYLE' &&
  1187. docEls[i].classList.contains('stylus') &&
  1188. docEls[i].id !== 'custom-native-player-style') {
  1189. $('html').insertBefore(style, docEls[i]);
  1190. break;
  1191. } else if (docEls[i] === docEls[docEls.length - 1]) {
  1192. $('html').insertBefore(style, docEls[i].nextSibling);
  1193. }
  1194. }
  1195. }
  1196.  
  1197. window.addEventListener('DOMContentLoaded', () => {
  1198. moveStyle();
  1199. setTimeout(moveStyle, 1000);
  1200.  
  1201. document.arrive(
  1202. 'video[controls], video[style*="visibility: inherit !important"]',
  1203. {fireOnAttributesModification: true, existing: true}, v => {
  1204. if (!v.parentNode.parentNode) return;
  1205. const vP = v.parentNode;
  1206. const vPP = v.parentNode.parentNode;
  1207. const imagus = !v.hasAttribute('controls') &&
  1208. $('html > div[style*="z-index: 2147483647"]') === v.parentNode;
  1209. const vidOrParentsIdOrClass =
  1210. `${v.id}${v.classList}${vP.id}${vP.classList}${vPP.id}${vPP.classList}`;
  1211. const exclude = v.classList.contains('custom-native-player') ||
  1212. v.classList.contains('imagus') ||
  1213. /(v(ideo)?|me)(-|_)?js|jw|jplay|plyr|kalt|flowp|wisti/i.test(vidOrParentsIdOrClass);
  1214. if (imagus || (v.hasAttribute('controls') && !exclude)) {
  1215. if (imagus) v.classList.add('imagus');
  1216. v.classList.add('custom-native-player');
  1217. v.classList.add('custom-native-player-hidden');
  1218. v.setAttribute('tabindex', '0');
  1219. v.setAttribute('preload', 'auto');
  1220. v.removeAttribute('controls');
  1221. customPlayer(v);
  1222. }
  1223. });
  1224. });
  1225.  
  1226. if (/^https?:\/\/www\.youtube\.com/.test(location.href)) {
  1227. document.arrive(
  1228. 'video[src*="youtube.com"]',
  1229. {fireOnAttributesModification: true, existing: true}, v => {
  1230. ytSaveCurrentTime(v);
  1231. });
  1232. }
  1233.  
  1234. const css = `.imagus-video-wrapper {
  1235. height: min-content!important;
  1236. position: fixed!important;
  1237. left: 0!important;
  1238. right: 0!important;
  1239. top: 0!important;
  1240. bottom: 0!important;
  1241. margin: auto!important;
  1242. box-shadow: none!important;
  1243. background-color: hsl(0, 0%, 0%)!important;
  1244. width: calc(100% - 100px)!important;
  1245. }
  1246. .imagus-video-wrapper.stickied {
  1247. box-shadow: 0 0 0 100000px hsla(0, 0%, 0%, .7)!important;
  1248. }
  1249. html > div[style*="2147483647"] div span {
  1250. cursor: auto!important;
  1251. }
  1252. .imagus-video-wrapper videowrapper {
  1253. height: 0!important;
  1254. padding-top: 56.25%!important;
  1255. }
  1256. .imagus-video-wrapper videowrapper video.custom-native-player {
  1257. position: absolute!important;
  1258. }
  1259. @media (min-width: 177.778vh) {
  1260. .imagus-video-wrapper {
  1261. margin: 18px auto!important;
  1262. height: calc(100vh - 18px)!important;
  1263. width: calc(((100vh - 18px) * 16) / 9)!important;
  1264. }
  1265. .imagus-video-wrapper videowrapper {
  1266. height: 100%!important;
  1267. padding-top: 0!important;
  1268. }
  1269. .imagus-video-wrapper videowrapper video.custom-native-player {
  1270. position: relative!important;
  1271. }
  1272. }
  1273. html > div[style*="2147483647"] > img[style*="display: block"] ~ videowrapper {
  1274. display: none!important;
  1275. }
  1276. html > div[style*="2147483647"] {
  1277. background: none!important;
  1278. box-shadow: none!important;
  1279. border: 0!important;
  1280. }
  1281. html > div[style*="2147483647"] videowrapper + div {
  1282. -webkit-text-fill-color: hsl(0, 0%, 90%)!important;
  1283. box-shadow: none!important;
  1284. width: 100%!important;
  1285. max-width: 100%!important;
  1286. box-sizing: border-box!important;
  1287. overflow: hidden!important;
  1288. text-overflow: ellipsis!important;
  1289. }
  1290. html > div:not(.stickied) video.custom-native-player + controls,
  1291. video[controls]:not(.custom-native-player) {
  1292. opacity: 0!important;
  1293. pointer-events: none!important;
  1294. }
  1295. videowrapper {
  1296. --wrapper-position: relative;
  1297. position: var(--wrapper-position)!important;
  1298. height: 100%!important;
  1299. display: block!important;
  1300. font-size: 0px!important;
  1301. top: 0!important;
  1302. bottom: 0!important;
  1303. left: 0!important;
  1304. right: 0!important;
  1305. background-color: hsl(0, 0%, 0%)!important;
  1306. overflow: hidden!important;
  1307. }
  1308. video.custom-native-player + controls timetooltip,
  1309. video.custom-native-player + controls volumetooltip {
  1310. position: absolute!important;
  1311. display: none!important;
  1312. top: -25px!important;
  1313. height: 22px!important;
  1314. line-height: 22px!important;
  1315. text-align: center!important;
  1316. border-radius: 4px!important;
  1317. font-size: 12px!important;
  1318. background: hsla(0, 0%, 0%, .7)!important;
  1319. box-shadow: 0 0 4px hsla(0, 0%, 100%, .5)!important;
  1320. color: hsl(0, 0%, 100%)!important;
  1321. pointer-events: none!important;
  1322. }
  1323. video.custom-native-player + controls timetooltip timetext {
  1324. color: hsl(0, 0%, 100%)!important;
  1325. }
  1326. video.custom-native-player + controls timetooltip.preview preview {
  1327. height: 113px!important;
  1328. width: 200px!important;
  1329. display: block!important;
  1330. }
  1331. video.custom-native-player + controls timetooltip.preview {
  1332. height: 138px!important;
  1333. width: 200px!important;
  1334. top: -142px!important;
  1335. margin-left: -100px!important;
  1336. }
  1337. video.custom-native-player + controls timetooltip {
  1338. margin-left: -25px!important;
  1339. width: 50px!important;
  1340. }
  1341. video.custom-native-player + controls volumetooltip {
  1342. margin-left: -20px!important;
  1343. width: 40px!important;
  1344. }
  1345. video.custom-native-player.compact + controls timeline timetooltip {
  1346. top: -25px!important;
  1347. }
  1348. video.custom-native-player.compact + controls btn,
  1349. video.custom-native-player.compact + controls rate,
  1350. video.custom-native-player.compact + controls volume {
  1351. height: 24px!important;
  1352. line-height: 22px!important;
  1353. }
  1354. video.custom-native-player.compact + controls volume input {
  1355. padding-bottom: 2px!important;
  1356. }
  1357. video.custom-native-player.compact + controls btn:before {
  1358. margin-top: -2px!important;
  1359. }
  1360. video.custom-native-player.compact + controls volume > volumebar {
  1361. top: 6px!important;
  1362. }
  1363. video.custom-native-player + controls timelinewrapper {
  1364. line-height: 20px!important;
  1365. }
  1366. video.custom-native-player + controls timeline:hover timetooltip:not(.hidden),
  1367. video.custom-native-player + controls volume:hover volumetooltip:not(.hidden) {
  1368. display: inline!important;
  1369. }
  1370. video.custom-native-player {
  1371. cursor: none!important;
  1372. max-height: 100%!important;
  1373. height: 100%!important;
  1374. width: 100%!important;
  1375. margin: 0!important;
  1376. padding: 0!important;
  1377. top: 0!important;
  1378. bottom: 0!important;
  1379. left: 0!important;
  1380. right: 0!important;
  1381. background-color: hsl(0, 0%, 0%)!important;
  1382. border-radius: 0!important;
  1383. }
  1384. video.custom-native-player:not(.contains-source):not([src*="/"]) {
  1385. cursor: auto!important;
  1386. }
  1387. video.custom-native-player:not(.contains-source):not([src*="/"]) + controls {
  1388. display: none!important;
  1389. }
  1390. video.custom-native-player + controls > * {
  1391. background: none!important;
  1392. outline: none!important;
  1393. line-height: 32px!important;
  1394. font-family: monospace!important;
  1395. }
  1396. video.custom-native-player.compact + controls > * {
  1397. line-height: 24px!important;
  1398. }
  1399. video.custom-native-player + controls {
  1400. --controls-z-index: 1;
  1401. white-space: nowrap!important;
  1402. transition: opacity .5s ease 0s!important;
  1403. background-color: hsla(0, 0%, 0%, .85)!important;
  1404. height: 32px !important;
  1405. width: 100%!important;
  1406. cursor: default !important;
  1407. font-size: 18px !important;
  1408. user-select: none!important;
  1409. z-index: var(--controls-z-index)!important;
  1410. flex: none!important;
  1411. position: absolute!important;
  1412. display: flex!important;
  1413. flex-wrap: wrap!important;
  1414. opacity: 0!important;
  1415. margin: 0!important;
  1416. bottom: 0!important;
  1417. left: 0!important;
  1418. right: 0!important;
  1419. }
  1420. video.custom-native-player.custom-native-player-hidden,
  1421. video.custom-native-player.custom-native-player-hidden + controls {
  1422. opacity: 0!important;
  1423. pointer-events: none!important;
  1424. }
  1425. video.custom-native-player.paused + controls,
  1426. video.custom-native-player.active + controls,
  1427. video.custom-native-player + controls:hover {
  1428. opacity: 1!important;
  1429. }
  1430. video.custom-native-player + controls timeline {
  1431. flex-grow: 1!important;
  1432. position: relative!important;
  1433. align-items: center!important;
  1434. flex-direction: column!important;
  1435. height: 100%!important;
  1436. }
  1437. video.custom-native-player + controls timelinewrapper {
  1438. flex: 1 0 480px!important;
  1439. position: relative!important;
  1440. align-items: center!important;
  1441. }
  1442. video.custom-native-player.compact + controls timelinewrapper {
  1443. order: -1;
  1444. flex-basis: 100%!important;
  1445. height: 20px!important;
  1446. }
  1447. video.custom-native-player.compact + controls timeline timebar {
  1448. top: 5px!important;
  1449. }
  1450. video.custom-native-player.compact + controls currenttime,
  1451. video.custom-native-player.compact + controls totaltime {
  1452. line-height: 20px!important;
  1453. }
  1454. video.custom-native-player.compact + controls {
  1455. height: 44px!important;
  1456. }
  1457. video.custom-native-player.compact-2 + controls btn.begin,
  1458. video.custom-native-player.compact-2 + controls btn.skip-short,
  1459. video.custom-native-player.compact-3 + controls rate,
  1460. video.custom-native-player.compact-3 + controls btn.rate-increase,
  1461. video.custom-native-player.compact-3 + controls btn.rate-decrease,
  1462. video.custom-native-player.compact-4 + controls btn.skip-long {
  1463. display: none!important;
  1464. }
  1465. video.custom-native-player + controls > * {
  1466. display: inline-flex!important;
  1467. }
  1468. video.custom-native-player.compact-2 + controls btn.rate-increase,
  1469. video.custom-native-player.compact-4 + controls btn.toggle-play {
  1470. margin-right: auto!important;
  1471. }
  1472. video.custom-native-player + controls timeline > timebar > timebuffer,
  1473. video.custom-native-player + controls timeline > timebar > timeprogress,
  1474. video.custom-native-player + controls volume > volumebar > volumetrail {
  1475. position: absolute!important;
  1476. flex: none!important;
  1477. pointer-events: none!important;
  1478. height: 100%!important;
  1479. border-radius: 20px!important;
  1480. }
  1481. video.custom-native-player + controls timeline > timebar,
  1482. video.custom-native-player + controls volume > volumebar {
  1483. position: absolute!important;
  1484. height: 10px!important;
  1485. border-radius: 20px!important;
  1486. overflow: hidden!important;
  1487. background-color: hsla(0, 0%, 16%, .85)!important;
  1488. top: 11px!important;
  1489. left: 0!important;
  1490. right: 0!important;
  1491. pointer-events: none!important;
  1492. z-index: -1!important;
  1493. box-shadow: inset 0 0 0 1px hsla(0, 0%, 40%), inset 0 0 5px hsla(0, 0%, 40%, .85)!important;
  1494. }
  1495. .audio-only.imagus-video-wrapper {
  1496. height: 58px!important;
  1497. padding: 0!important;
  1498. }
  1499. .audio-only.imagus-video-wrapper.compact {
  1500. height: 78px!important;
  1501. }
  1502. .audio-only.imagus-video-wrapper video.custom-native-player + controls {
  1503. opacity: 1!important;
  1504. }
  1505. .audio-only.imagus-video-wrapper video.custom-native-player {
  1506. height: 0!important;
  1507. opacity: 0!important;
  1508. min-height: 0!important;
  1509. }
  1510. .audio-only.imagus-video-wrapper.stickied {
  1511. box-shadow: none!important;
  1512. }
  1513. .audio-only.imagus-video-wrapper videowrapper {
  1514. height: 58px!important;
  1515. top: auto!important;
  1516. padding:0!important;
  1517. }
  1518. .audio-only.imagus-video-wrapper.compact {
  1519. top: 18px!important;
  1520. bottom: auto!important;
  1521. }
  1522. .audio-only.imagus-video-wrapper.compact videowrapper {
  1523. height: 78px!important;
  1524. }
  1525. video.custom-native-player + controls volume.disabled,
  1526. video.custom-native-player + controls btn.disabled {
  1527. -webkit-filter: brightness(.4);
  1528. filter: brightness(.4);
  1529. pointer-events: none!important;
  1530. }
  1531. video.custom-native-player.disabled,
  1532. video.custom-native-player.active.disabled {
  1533. cursor: default!important;
  1534. }
  1535. video.custom-native-player.disabled + controls {
  1536. opacity: 1!important;
  1537. -webkit-filter: brightness(.3)sepia(1)hue-rotate(320deg)saturate(5);
  1538. filter: brightness(.3)sepia(1)hue-rotate(320deg)saturate(5);
  1539. }
  1540. video.custom-native-player.disabled + controls > * {
  1541. pointer-events: none!important;
  1542. }
  1543. video.custom-native-player + controls volume {
  1544. max-width: 70px!important;
  1545. flex: 1 0 48px!important;
  1546. position: relative!important;
  1547. margin: 0 12px!important;
  1548. }
  1549. video.custom-native-player + controls timeline > timebar > timebuffer {
  1550. background-color: hsla(0, 0%, 100%, .2)!important;
  1551. border-top-right-radius: 20px!important;
  1552. border-bottom-right-radius: 20px!important;
  1553. left: 0!important;
  1554. }
  1555. video.custom-native-player + controls timeline > timebar > timeprogress,
  1556. video.custom-native-player + controls volume > volumebar > volumetrail {
  1557. background-color: hsla(0, 0%, 100%, .4)!important;
  1558. left: 0!important;
  1559. }
  1560. video.custom-native-player + controls volume.disabled volumetrail {
  1561. width: 0!important;
  1562. }
  1563. video.custom-native-player + controls timeline > input {
  1564. height: 100%!important;
  1565. width: 100%!important;
  1566. }
  1567. video.custom-native-player.active {
  1568. cursor: pointer!important;
  1569. }
  1570. video.custom-native-player + controls btn {
  1571. border: none !important;
  1572. cursor: pointer!important;
  1573. background-color: transparent!important;
  1574. font-family: "Segoe UI Symbol"!important;
  1575. font-size: 0!important;
  1576. margin: 0!important;
  1577. align-items: center!important;
  1578. justify-content: center!important;
  1579. height: 32px!important;
  1580. padding: 0!important;
  1581. flex: 1 1 32px!important;
  1582. max-width: 46px!important;
  1583. box-sizing: content-box!important;
  1584. position: relative!important;
  1585. opacity: .86!important;
  1586. text-shadow: none!important;
  1587. transition: opacity .3s, text-shadow .3s!important;
  1588. -webkit-text-fill-color: hsl(0, 0%, 100%)!important;
  1589. }
  1590. video.custom-native-player + controls btn.toggle-play {
  1591. flex: 1 1 46px!important
  1592. }
  1593. video.custom-native-player + controls btn:hover {
  1594. opacity: 1!important;
  1595. text-shadow: 0 0 8px hsla(0, 0%, 100%)!important;
  1596. }
  1597. video.custom-native-player.playback-rate-increased + controls btn.rate-increase,
  1598. video.custom-native-player.playback-rate-decreased + controls btn.rate-decrease {
  1599. -webkit-text-fill-color: cyan!important;
  1600. }
  1601. video.custom-native-player + controls rate {
  1602. height: 32px!important;
  1603. width: 42px!important;
  1604. margin: 0!important;
  1605. display: unset!important;
  1606. text-align: center!important;
  1607. font-size: 14px!important;
  1608. flex-shrink: 0!important;
  1609. font-weight: bold!important;
  1610. letter-spacing: .5px!important;
  1611. -webkit-text-fill-color: hsl(0, 0%, 100%)!important;
  1612. user-select: none!important;
  1613. pointer-events: none!important;
  1614. opacity: .86!important;
  1615. }
  1616. video.custom-native-player + controls rate[data-current-rate] {
  1617. pointer-events: all!important;
  1618. cursor: pointer!important;
  1619. }
  1620. video.custom-native-player + controls rate[data-current-rate]:hover {
  1621. transition: opacity .3s, text-shadow .3s!important;
  1622. opacity: 1!important;
  1623. text-shadow: 0 0 8px hsla(0, 0%, 100%)!important;
  1624. }
  1625. video.custom-native-player + controls input[type=range] {
  1626. -webkit-appearance: none!important;
  1627. background-color: transparent !important;
  1628. outline: none!important;
  1629. border: 0!important;
  1630. border-radius: 6px !important;
  1631. margin: 0!important;
  1632. top: 0!important;
  1633. bottom: 0!important;
  1634. left: 0!important;
  1635. right: 0!important;
  1636. padding: 0!important;
  1637. width: 100%!important;
  1638. position: relative!important;
  1639. }
  1640. video.custom-native-player + controls input[type='range']::-webkit-slider-thumb {
  1641. -webkit-appearance: none!important;
  1642. background-color: hsl(0, 0%, 86%)!important;
  1643. border: 0!important;
  1644. height: 14px!important;
  1645. width: 14px!important;
  1646. border-radius: 50%!important;
  1647. pointer-events: none!important;
  1648. }
  1649. video.custom-native-player.muted + controls volume input[type='range']::-webkit-slider-thumb {
  1650. background-color: hsl(0, 0%, 50%)!important;
  1651. }
  1652. video.custom-native-player + controls input[type='range']::-moz-range-thumb {
  1653. -moz-appearance: none!important;
  1654. background-color: hsl(0, 0%, 86%)!important;
  1655. border: 0!important;
  1656. height: 14px!important;
  1657. width: 14px!important;
  1658. border-radius: 50%!important;
  1659. pointer-events: none!important;
  1660. }
  1661. video.custom-native-player.muted + controls volume input[type='range']::-moz-range-thumb {
  1662. background-color: hsl(0, 0%, 50%)!important;
  1663. }
  1664. video.custom-native-player + controls currenttime,
  1665. video.custom-native-player + controls totaltime {
  1666. font-family: monospace, arial!important;
  1667. font-weight: bold!important;
  1668. font-size: 14px!important;
  1669. letter-spacing: .5px!important;
  1670. height: 100%!important;
  1671. line-height: 32px!important;
  1672. min-width: 58px!important;
  1673. display: unset!important;
  1674. -webkit-text-fill-color: hsl(0, 0%, 86%)!important;
  1675. }
  1676. video.custom-native-player + controls btn.rate-decrease,
  1677. video.custom-native-player + controls btn.rate-increase {
  1678. padding: 0!important;
  1679. flex: 1 0 14px!important;
  1680. max-width: 24px!important;
  1681. }
  1682. video.custom-native-player + controls btn.rate-decrease {
  1683. margin-left: auto!important;
  1684. }
  1685. video.custom-native-player + controls currenttime {
  1686. padding: 0 12px 0 0!important;
  1687. margin-right: 2px!important;
  1688. text-align: right!important;
  1689. }
  1690. video.custom-native-player + controls totaltime {
  1691. padding: 0 0 0 12px!important;
  1692. margin-left: 2px!important;
  1693. text-align: left!important;
  1694. }
  1695. .direct-video-top-level {
  1696. margin: 0!important;
  1697. padding: 0!important;
  1698. display: flex!important;
  1699. align-items: center!important;
  1700. justify-content: center!important;
  1701. }
  1702. .direct-video-top-level video {
  1703. height: calc(((100vw - 30px) * 9) / 16)!important;
  1704. min-height: calc(((100vw - 30px) * 9) / 16)!important;
  1705. max-height: calc(((100vw - 30px) * 9) / 16)!important;
  1706. width: calc(100vw - 30px)!important;
  1707. min-width: calc(100vw - 30px)!important;
  1708. max-width: calc(100vw - 30px)!important;
  1709. margin: auto!important;
  1710. }
  1711. .direct-video-top-level > video.custom-native-player + controls {
  1712. position: absolute!important;
  1713. left: 0!important;
  1714. right: 0!important;
  1715. margin: 0 auto !important;
  1716. width: calc(100vw - 30px)!important;
  1717. bottom: calc((100vh - (((100vw - 30px) * 9) / 16)) / 2)!important;
  1718. }
  1719. @media (min-width: 177.778vh) {
  1720. .direct-video-top-level video {
  1721. position: unset!important;
  1722. height: calc(100vh - 30px)!important;
  1723. min-height: calc(100vh - 30px)!important;
  1724. max-height: calc(100vh - 30px)!important;
  1725. width: calc(((100vh - 30px) * 16) / 9)!important;
  1726. min-width: calc(((100vh - 30px) * 16) / 9)!important;
  1727. max-width: calc(((100vh - 30px) * 16) / 9)!important;
  1728. margin: 0 auto!important;
  1729. padding: 0!important;
  1730. background-color: hsl(0, 0%, 0%)!important;
  1731. }
  1732. .direct-video-top-level > video.custom-native-player + controls {
  1733. width: calc(((100vh - 30px) * 16) / 9)!important;
  1734. min-width: calc(((100vh - 30px) * 16) / 9)!important;
  1735. max-width: calc(((100vh - 30px) * 16) / 9)!important;
  1736. bottom: 15px!important;
  1737. }
  1738. }
  1739. video::-webkit-media-controls,
  1740. .native-fullscreen > *:not(video):not(controls) {
  1741. display: none!important;
  1742. }
  1743. .native-fullscreen video.custom-native-player,
  1744. .direct-video-top-level .native-fullscreen video.custom-native-player {
  1745. height: 100vh!important;
  1746. width: 100vw!important;
  1747. max-height: 100vh!important;
  1748. max-width: 100vw!important;
  1749. min-height: 100vh!important;
  1750. min-width: 100vw!important;
  1751. margin: 0!important;
  1752. }
  1753. .native-fullscreen video.custom-native-player + controls {
  1754. position: fixed!important;
  1755. bottom: 0!important;
  1756. left: 0!important;
  1757. right: 0!important;
  1758. margin: 0!important;
  1759. width: 100vw!important;
  1760. max-width: 100vw!important;
  1761. }
  1762. video.custom-native-player + controls btn:before {
  1763. font-family: 'customNativePlayer'!important;
  1764. font-weight: 400!important;
  1765. -webkit-font-smoothing: subpixel-antialiased!important;
  1766. -moz-osx-font-smoothing: subpixel-antialiased!important;
  1767. font-size: 20px!important;
  1768. }
  1769. video.custom-native-player + controls btn.skip-short.right:before {
  1770. content: '\\e90c'!important;
  1771. }
  1772. video.custom-native-player + controls btn.skip-short.left:before {
  1773. content: '\\e90b'!important;
  1774. }
  1775. video.custom-native-player + controls btn.skip-long.right:before {
  1776. content: '\\e901'!important;
  1777. }
  1778. video.custom-native-player + controls btn.skip-long.left:before {
  1779. content: '\\e902'!important;
  1780. }
  1781. video.custom-native-player + controls btn.begin:before {
  1782. content: '\\e908'!important;
  1783. }
  1784. video.custom-native-player + controls btn.toggle-play:before {
  1785. content: '\\e906'!important;
  1786. font-size: 26px!important;
  1787. }
  1788. video.custom-native-player.playing + controls btn.toggle-play:before {
  1789. content: '\\e905'!important;
  1790. font-size: 24px!important;
  1791. }
  1792. video.custom-native-player + controls btn.rate-decrease:before {
  1793. content: '\\ea0b'!important;
  1794. font-size: 10px!important;
  1795. }
  1796. video.custom-native-player + controls btn.rate-increase:before {
  1797. content: '\\ea0a'!important;
  1798. font-size: 10px!important;
  1799. }
  1800. video.custom-native-player + controls btn.mute:before {
  1801. content: '\\e90a'!important;
  1802. font-size: 22px!important;
  1803. }
  1804. video.custom-native-player.muted + controls btn.mute:before {
  1805. content: '\\e909'!important;
  1806. font-size: 22px!important;
  1807. }
  1808. video.custom-native-player + controls btn.mute.disabled:before {
  1809. content: '\\e909'!important;
  1810. }
  1811. video.custom-native-player + controls btn.expand:before {
  1812. content: '\\e904'!important;
  1813. font-size: 24px!important;
  1814. }
  1815. .native-fullscreen video.custom-native-player + controls btn.expand:before {
  1816. content: '\\e903'!important;
  1817. font-size: 24px!important;
  1818. }
  1819. @font-face {
  1820. font-family: 'customNativePlayer';
  1821. src: url(data:application/x-font-woff;charset=utf-8;base64,d09GRgABAAAAAAe8AAsAAAAAC2gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABPUy8yAAABCAAAAEAAAABgDxIHPmNtYXAAAAFIAAAAUQAAAHTquqeaZ2FzcAAAAZwAAAAIAAAACAAAABBnbHlmAAABpAAABG0AAAcgC+w8l2hlYWQAAAYUAAAALAAAADYWP5TBaGhlYQAABkAAAAAcAAAAJAgCBBhobXR4AAAGXAAAAC8AAABcUkALAGxvY2EAAAaMAAAAMAAAADAN9g+EbWF4cAAABrwAAAAYAAAAIAAcAIxuYW1lAAAG1AAAANoAAAGGmUoJ+3Bvc3QAAAewAAAADAAAACAAAwAAeNpjYGZ+xTiBgZWBgWkm0xkGBoZ+CM34msGYkZMBFTAKoAkwODAwvNJiPvD/AIMD8wEQj4ERSVaBgQEAdCILXHjaY2BgYGaAYBkGRijNDGSBaBaGCCAtxCAAFGECiim85HnZ84r7ldorrf9///9nAAGFlwwvu19xwcUY/z8WZxFrE+MUfS/6BmoSGgAA0DQY1QAAAAABAAH//wAPeNqNVD1s20YUfo+UdJYd/dAiRdtKJVOMScWyKVs0SRuuGQ6xA8QI4CKQ4p+kMAJkSAx0SacOBdGtKNBNnTUFhTQUKNDOHDp5l5cu3r0nSyz1kZSNGHCCHqS7e3/f+967OwLC1eAAnI1I/P+6AXT4OncBUyQogiooliKYgsLXR9Aekb2NgJ3xZjSO7kPAd7gAeGCElEYBhTT28c3wN/TDOaAYGJLjEDBOy8EJxbQohoMkwIKACkUN4oCAI+RRyAoS13xSkIECzAIUTMm0VKmgRguaFi0FK5QGfvvM98+IWJvm9hlKoUAbf7jok5YkuIGZpCoFkKnSCIyPsMZ7KUyDdQpuExoXBvsEckKIBDYEgvfJENZCFXV4ILyo/gVTUMOWIfT72Op3uPZljwsTI7bGeakyqhZbeMZdXPawHvUdyYYhBvXdon6HUdhph7Y+eHyL70CDBIvJVlMuo1yURJZFllKruoG6ZqlipDWbjouOba1FWpWDwcBqGDsijR2jYcX71lzphes+euS6L0pz8Z676A0GPVHcbpCT0diWRFHabhjWzgP3eYnGc/fBTuRfinvoEyef92ACKtAEm5itaboku2iZYoqFa8xAl4oxW2SyKpiyIBNpiSjKDiapFi7YXHmNeHJnypNkubjnOF5D1zfy+ctf7NPT/uAvaaW0tFd9Zl/a+PjgAIONo5lvX7MMK6+XvNrBykPXfamq2f3M3dKuYZjo26cjambl7/zcxP5krfTM5k7rBwd/AnXWh8fE2Y7u0hLdpJAOU5NEXHCRvyIat5VJ9qeN1P3+YNDnvM2Vlc2TmGA+v6HrDc9x9opj4pxHqbnewCeOw99njvCPK1qmYeyW7mb2s6r60nUfjkmHd+JrCLh30TuAhTRy7+gJvIneC9kOyfbPtQ0Pr99SqBkFCeCDqBa6TTrTHZ1nsiLITgK6wXHQ7Qbd4XE34INwJvmS/kja8Yu/JR7jeAwif/48BkB/DIDn1wB4Ha9G34k1rY7VlCQo1dRXKBZNRRCLm9i0LUFp2lt0NfjzYbeQCTKFYTdTKGTwOBLwmATOi5bMbQ7j7xR6CeA8yNGZSSF6jKlSNihk+CAM+OhlCtx8tA2n6I6Gk8f/CHX4Br6Dn6mLVU3X1pybJxsqmvLNw8+iql/52mufd1q93asoRmZW1RqoVjVLWLM3kZJSuCSIoYn/IT3Nsllldq6aplGdm1Wy2WwtWytX7k/RuF8p19h0ujcpkNfqzOzszCrZ9WxlRp5PT0yk5+WZChPS/QilnM/l8uUofkkuFuUlNv1r6k7y/duwG2/fs0I6PTWV5lMaY+SiaNrT5WXDWF5+qmkKKShu2Xhl2+vrtv3KWK4xdsgmKFdzy/1py23SLpcrq/eeLC7W64uLT+6p5Ql2FEGVdW1P08sRxtLG+vfrG0uM/ZtMfKADpPP4kErwifzkx2Ayn8Dxd58GH9CZ5GCRzlVSdaZajm6ZsmNKDL/QsKB1cnL1G+7eVh62PnXxPkPjP6LOXdEAAAB42mNgZAADZqYpmfH8Nl8ZuFnA/JsFK5QQ9P8DLA7MB4BcDgYmkCgA/hcJqHjaY2BkYGA+8P8AAwOLAwMDmGRkQAXiAFdpAyR42mNhQAAmIGZhYLgKxKuBOBvKBmJGoDhjKJJcAwQz2gBxFAtEHwI7QGgAfJcHlwAAAAAAAAoAFAAeADgAUgBmAJAAuADMANoA6AEyAYwB1gHkAfICEgIyAmgChANOA5B42mNgZGBgEGfoYmBhAAEmBjQAABCkAKl42m3OwWrCQBSF4T8aLbXgri5czRMEhdJdt4IUNy5cN8YhBHQGxmQh9An6HF33GXuMd5mBDF/O3Ll3gDl/ZNxXxlO/39dI/jKP5XdzLnfmCS+8mqfKP80zlvzoVpY/K5nr5OGRXJvH8oc5l7/NExY481T53jzjjd+mipcYAw0VkYu+SDj4dG1icOtixQFP4qoCHajPmoLV4K3BcO/r7lwmDfV6aMeZkjRYuYmhdbUPPpWtP7njzW2ruFNZwaaf3Wp6rTahf1Gpf89J2ZGb9m3fa/foRfEP3IM9twAAeNpjYGbACwAAfQAE) format('woff'), url('customNativePlayer.ttf') format('truetype'), url('customNativePlayer.svg#customNativePlayer') format('svg');
  1822. font-weight: normal;
  1823. font-style: normal;
  1824. }`;
  1825.  
  1826. const style = newEl('style');
  1827. style.id = 'custom-native-player-style';
  1828. style.type = 'text/css';
  1829. style.className = 'stylus'; // Dark Reader won't brute force override stylus
  1830. style.textContent = css;
  1831. document.documentElement.appendChild(style);