Greasy Fork is available in English.

Youtube Speed By Channel

Allow to choose the default speed for specific YT channel

  1. // ==UserScript==
  2. // @name Youtube Speed By Channel
  3. // @namespace Alpe
  4. // @version 0.2.15
  5. // @description Allow to choose the default speed for specific YT channel
  6. // @author Alpe
  7. // @include https://www.youtube.com/*
  8. // @grant GM.setValue
  9. // @grant GM.getValue
  10. // @grant GM.deleteValue
  11. // @grant GM_registerMenuCommand
  12. // @grant GM_getResourceText
  13. // @run-at document-start
  14. // @resource jquery https://code.jquery.com/jquery-3.7.1.min.js
  15. // ==/UserScript==
  16.  
  17. (async () => {
  18. if (window.trustedTypes && window.trustedTypes.createPolicy){
  19. try {
  20. trustedTypes?.createPolicy?.('default', {createScriptURL: s => s, createScript: s => s, createHTML: s => s});
  21. } catch {}
  22. }
  23. eval(GM_getResourceText('jquery'));
  24. const defaults = {
  25. DEFAULT_SPEED: 1.0,
  26. SHOW_RELATIVE_TIME: true,
  27. COLOR_SELECTED: "red",
  28. COLOR_NORMAL: "rgb(220,220,220)",
  29. BUTTON_TEMPLATES: JSON.stringify([
  30. ["50%", 0.5],
  31. ["75%", 0.75],
  32. ["Normal", 1],
  33. ["1.25x", 1.25],
  34. ["1.5x", 1.5],
  35. ["1.75x", 1.75],
  36. ["2x", 2],
  37. ["2.25x", 2.25],
  38. ["2.5x", 2.5],
  39. ["3x", 3],
  40. ["3.5x", 3.5]
  41. ]),
  42. AUDIO_BOOST: 1,
  43. SAVE_RESUME_TIME: false,
  44. SHOW_ON_PLAYER: false
  45. }
  46.  
  47. for (let name in defaults) {
  48. let value = defaults[name];
  49. window[name] = (name === "BUTTON_TEMPLATES" ? JSON.parse(await GM.getValue("_YSC-" + name,value)) : await GM.getValue("_YSC-" + name,value));
  50. }
  51.  
  52. async function toggleconfig(name,e){
  53. e = e||!(await GM.getValue("_YSC-" + name,defaults[name]));
  54. GM.setValue("_YSC-" + name,e);
  55. alert(name + ': ' + e);
  56. }
  57.  
  58. if (typeof GM_registerMenuCommand == 'undefined') {
  59. this.GM_registerMenuCommand = (caption, commandFunc, accessKey) => {
  60. if (!document.body) {
  61. if (document.readyState === 'loading'
  62. && document.documentElement && document.documentElement.localName === 'html') {
  63. new MutationObserver((mutations, observer) => {
  64. if (document.body) {
  65. observer.disconnect();
  66. GM_registerMenuCommand(caption, commandFunc, accessKey);
  67. }
  68. }).observe(document.documentElement, {childList: true});
  69. } else {
  70. console.error('GM_registerMenuCommand got no body.');
  71. }
  72. return;
  73. }
  74. let contextMenu = document.body.getAttribute('contextmenu');
  75. let menu = (contextMenu ? document.querySelector('menu#' + contextMenu) : null);
  76. if (!menu) {
  77. menu = document.createElement('menu');
  78. menu.setAttribute('id', 'gm-registered-menu');
  79. menu.setAttribute('type', 'context');
  80. document.body.appendChild(menu);
  81. document.body.setAttribute('contextmenu', 'gm-registered-menu');
  82. }
  83. let menuItem = document.createElement('menuitem');
  84. menuItem.textContent = caption;
  85. menuItem.addEventListener('click', commandFunc, true);
  86. menu.appendChild(menuItem);
  87. };
  88. }
  89.  
  90.  
  91. $.each([
  92. ["List current settings", async function(){
  93. var set = [];
  94. for (let name in defaults) {
  95. let value = defaults[name];
  96. set.push(name + ' = ' + await GM.getValue('_YSC-' + name,value) + (( await GM.getValue('_YSC-' + name,value)!=defaults[name])?" [default is " + defaults[name] + "]":""));
  97. }
  98. alert(set.join('\n'));
  99. }],
  100. ["Configure default speed", async function(){
  101. var temp = prompt("Default: " + defaults['DEFAULT_SPEED'], await GM.getValue('_YSC-DEFAULT_SPEED',DEFAULT_SPEED));
  102. if (temp === null) return;
  103. if (temp.length === 0){
  104. GM.deleteValue('_YSC-DEFAULT_SPEED');
  105. alert("default restored");
  106. return;
  107. }
  108. temp = parseFloat(temp);
  109. if (!isNaN(temp)) toggleconfig('DEFAULT_SPEED',temp);
  110. }],
  111. ["Show time relative to speed", async function(){
  112. var temp = prompt("Default: " + defaults['SHOW_RELATIVE_TIME'], await GM.getValue('_YSC-SHOW_RELATIVE_TIME',SHOW_RELATIVE_TIME));
  113. if (temp === null) return;
  114. if (temp.length === 0){
  115. GM.deleteValue('_YSC-SHOW_RELATIVE_TIME');
  116. alert("default restored");
  117. return;
  118. }
  119. if (temp === "true" || temp === "false") toggleconfig('SHOW_RELATIVE_TIME', (temp === "true"));
  120. }],
  121. ["Configure Color for the selected speed", async function(){
  122. var temp = prompt("Default: " + defaults['COLOR_SELECTED'], await GM.getValue('_YSC-COLOR_SELECTED',COLOR_SELECTED));
  123. if (temp === null) return;
  124. if (temp.length === 0){
  125. GM.deleteValue('_YSC-COLOR_SELECTED');
  126. alert("default restored");
  127. return;
  128. }
  129. toggleconfig('COLOR_SELECTED',temp);
  130. }],
  131. ["Configure color for unselected speed", async function(){
  132. var temp = prompt("Default: " + defaults['COLOR_NORMAL'], await GM.getValue('_YSC-COLOR_NORMAL',COLOR_NORMAL));
  133. if (temp === null) return;
  134. if (temp.length === 0){
  135. GM.deleteValue('_YSC-COLOR_NORMAL');
  136. alert("default restored");
  137. return;
  138. }
  139. toggleconfig('COLOR_NORMAL',temp);
  140. }],
  141. ["Configure Buttons", async function(){
  142. var temp = prompt("What buttons should be displayed.\nformat: [text,speed]\neg: [half,0.5][normal,1][double,2]", '[' + JSON.parse(await GM.getValue('_YSC-BUTTON_TEMPLATES',JSON.stringify(BUTTON_TEMPLATES))).join('],[') + ']');
  143. if (temp === null) return;
  144. if (temp.length === 0){
  145. GM.deleteValue('_YSC-BUTTON_TEMPLATES');
  146. alert("default restored");
  147. return;
  148. }
  149. var match = temp.match(/\[[^,]+,[^\]]+\]/g);
  150. if (!match){
  151. alert("invalid option");
  152. } else {
  153. var array = [];
  154. for (let i=0; i < match.length; i++){
  155. let match2 = match[i].match(/\[([^,]+),([^\]]+)/);
  156. array.push([match2[1], parseFloat(match2[2])])
  157. }
  158. toggleconfig('BUTTON_TEMPLATES',JSON.stringify(array));
  159. }
  160. }],
  161. ["Configure audio boost", async function(){
  162. var temp = prompt("Can be any number bigger than 1.\n\n1 = function disabled\n1.5 = 50% boost\n2 = 100% boost\n\n\nDefault: " + defaults['AUDIO_BOOST'], await GM.getValue('_YSC-AUDIO_BOOST',AUDIO_BOOST));
  163. if (temp === null) return;
  164. if (temp.length === 0){
  165. GM.deleteValue('_YSC-AUDIO_BOOST');
  166. alert("default restored");
  167. return;
  168. }
  169. temp = parseFloat(temp);
  170. if (!isNaN(temp) && temp >= 1){
  171. toggleconfig('AUDIO_BOOST',temp);
  172. window['AUDIO_BOOST'] = temp;
  173. $(temp === 1 ? 'video[vsb-audioboost]' : 'video').each(function(){
  174. audioboost(this, true);
  175. });
  176. }
  177. }],
  178. ["Save resume time to url", async function(){
  179. var temp = prompt("Can be true or false\nIf true, it updates the url every 5 seconds with &t= parameter. So if you close the browser or tab and reopen it, the video will start playing close to where you stopped it.\n\nDefault: " + defaults['SAVE_RESUME_TIME'], await GM.getValue('_YSC-SAVE_RESUME_TIME',SAVE_RESUME_TIME));
  180. if (temp === null) return;
  181. if (temp.length === 0){
  182. GM.deleteValue('_YSC-SAVE_RESUME_TIME');
  183. alert("default restored");
  184. return;
  185. }
  186. if (temp === "true" || temp === "false") toggleconfig('SAVE_RESUME_TIME', (temp === "true"));
  187. }],
  188. ["Show speed controls on top of the player instead of below the progress bar", async function(){
  189. var temp = prompt("Can be true or false\n\nDefault: " + defaults['SHOW_ON_PLAYER'], await GM.getValue('_YSC-SHOW_ON_PLAYER',SHOW_ON_PLAYER));
  190. if (temp === null) return;
  191. if (temp.length === 0){
  192. GM.deleteValue('_YSC-SHOW_ON_PLAYER');
  193. alert("default restored");
  194. return;
  195. }
  196. if (temp === "true" || temp === "false") toggleconfig('SHOW_ON_PLAYER', (temp === "true"));
  197. }],
  198. ["Restore default",function(){
  199. for (let name in defaults) {
  200. GM.deleteValue('_YSC-' + name);
  201. }
  202. alert("Default restored");
  203. }]
  204. ], function(a,b){ GM_registerMenuCommand(b[0],b[1]); });
  205.  
  206. var stateKey, eventKey;
  207. {
  208. let keys = {
  209. hidden: "visibilitychange",
  210. webkitHidden: "webkitvisibilitychange",
  211. mozHidden: "mozvisibilitychange",
  212. msHidden: "msvisibilitychange"
  213. }
  214. for (stateKey in keys) {
  215. if (stateKey in document) {
  216. eventKey = keys[stateKey];
  217. break;
  218. }
  219. }
  220. }
  221.  
  222. function vis (c) {
  223. if (c) document.addEventListener(eventKey, c);
  224. return !document[stateKey];
  225. }
  226.  
  227. function getspeed(params = {}){
  228. let speed, reason;
  229. if (params.hasOwnProperty('force1x') && params.force1x){
  230. speed = 1;
  231. reason = "forcing 1x (live?)";
  232. } else if (params.hasOwnProperty('channelspeed') && typeof params.channelspeed === "number"){
  233. speed = params.channelspeed;
  234. reason = "channelspeed";
  235. } else if (params.hasOwnProperty('defspeed') && Number.isInteger(params.defspeed)){
  236. speed = params.defspeed;
  237. reason = "overwritten default (music?)";
  238. } else {
  239. speed = DEFAULT_SPEED;
  240. reason = "default";
  241. }
  242. if (params.channelspeed === undefined) delete params.channelspeed;
  243. if (params.defspeed === null) delete params.defspeed;
  244. if (params.force1x === false) delete params.force1x;
  245. params['chosenspeed'] = speed;
  246. params['chosenreason'] = reason;
  247. console.log(params);
  248. return speed;
  249. }
  250.  
  251. function buttonclick(evt){
  252. let id = evt.target.parentNode.id.match(/\d+$/)[0],
  253. el = $(evt.target);
  254. el.parent().children(":not([title])").css("color", COLOR_NORMAL);
  255. el.css("color", COLOR_SELECTED);
  256. if ($('video[vsb-video="' + id + '"]').length === 0){
  257. youtubefix();
  258. }
  259. try {
  260. let video = $('video[vsb-video=' + id + ']')[0];
  261. video.playbackRate = parseFloat(el.attr('speed'));
  262. if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) changetime(video);
  263. } catch (err){
  264. console.log('error on buttonclick()', evt, err);
  265. setTimeout(function(){ buttonclick(evt); }, 1000);
  266. }
  267. }
  268.  
  269. function getchannelname (id, div = null){
  270. try {
  271. if (div === null) div = $('#channel-name[vsb-channel=' + id + ']');
  272. let channel = div.find('#container #text:visible:first').text().trim();
  273. if (!channel){
  274. channel = div.find('.ytd-channel-name').find('a').text().trim();
  275. }
  276. if (!channel){
  277. let metavid = document.querySelector('#watch7-content > meta[itemprop=videoId]');
  278. if (metavid !== null && (new URL(div.closest('ytd-watch-flexy, ytd-browse').find('video')[0].baseURI)).searchParams.get('v') === metavid.getAttribute('content')){
  279. let metaname = document.querySelector('#watch7-content > span[itemprop=author] > link[itemprop=name]');
  280. if (metaname !== null) channel = metaname.getAttribute('content');
  281. if (!channel) channel = '';
  282. }
  283. }
  284. return channel;
  285. } catch (e) {
  286. console.log("error", e);
  287. return '';
  288. }
  289. }
  290.  
  291. function setchanneldefault(el){
  292. let id = el.target.parentNode.id.match(/\d+$/)[0];
  293. let channel = getchannelname(id);
  294. changebuttontitle(id, channel);
  295. let currentspeed = $('video[vsb-video=' + id + ']')[0].playbackRate;
  296. el = $(el.target).parent();
  297. el.children().css("text-decoration", "").filter('span[speed="' + currentspeed + '"]').css("text-decoration", "underline");
  298. GM.setValue(channel, currentspeed);
  299. console.log('changing default for (' + channel + ') to (' + currentspeed + ')');
  300. }
  301.  
  302. function createcontainer(curspeed, id){
  303. let div = document.createElement("div");
  304. let prev_node = null;
  305.  
  306. div.id = "vsb-container" + id;
  307. div.style.marginBottom = "0px";
  308. div.style.paddingBottom = "0px";
  309. div.style.float = "left";
  310. div.style.fontWeight = "bold";
  311. div.style.fontSize = "80%";
  312.  
  313. div.innerHTML += '<span style="margin-right: 10px; color: white; cursor: pointer;" title="Set current speed as default for this channel">setdefault</span>';
  314. BUTTON_TEMPLATES.forEach(function(button){
  315. div.innerHTML += '<span style="margin-right: 10px; color: ' + (curspeed === button[1] ? COLOR_SELECTED : COLOR_NORMAL) + '; cursor: pointer;" speed="' + button[1] + '">' + button[0] + '</span>';
  316. });
  317.  
  318. $('span:not([title])', div).on( "click", buttonclick);
  319. $('span[title]', div).on( "click", setchanneldefault);
  320.  
  321. return div;
  322. }
  323.  
  324. window.vsbid = 0;
  325.  
  326. function getid(){
  327. let id = window.vsbid;
  328. window.vsbid++;
  329. return id;
  330. }
  331.  
  332. function changebuttontitle(id, channelname = ''){
  333. let container = $('#vsb-container' + id + ' > span[title]');
  334. if (container.length > 0){
  335. container[0].title = container[0].title.split(' [')[0] + (channelname !== '' ? ' [' + channelname + ']' : '');
  336. }
  337. }
  338.  
  339. function ob_youtube_movieplayer (mutationsList, observer){
  340. for(let mutation of mutationsList) {
  341. if (mutation.attributeName === 'video-id'){
  342. let el = $('[id^=vsb-container]', mutation.target);
  343. if (el.length === 0){
  344. alert('fixing');
  345. console.log('fixing this');
  346. youtube();
  347. el = $('[id^=vsb-container]', mutation.target);
  348. }
  349. let id = el[0].id.match(/\d+$/)[0];
  350. let channeldiv = $('#channel-name[vsb-channel="' + id + '"]');
  351. if($('span[speed="1"]', el).click().length === 0) $('video[vsb-video="' + id + '"]')[0].playbackRate = 1;
  352. $('span', el).css("text-decoration", "");
  353. changebuttontitle(id);
  354. setTimeout(async function(){
  355. let channelspeed, channelname = getchannelname(id, channeldiv);
  356. let tries = 1;
  357. while(channelname === '' && tries <= 8){
  358. if (tries === 1){
  359. alert('sleeping');
  360. console.log("id", id);
  361. console.log("channeldiv", channeldiv);
  362. console.log("channelname", channelname);
  363. }
  364. console.log('sleeping ' + tries, channeldiv);
  365. await (new Promise(resolve => setTimeout(resolve, 200)));
  366. channelname = getchannelname(id, channeldiv);
  367. tries++;
  368. }
  369. if (channelname !== ''){
  370. channelspeed = await GM.getValue(channelname);
  371. } else {
  372. channelspeed = undefined;
  373. }
  374. changebuttontitle(id, channelname);
  375. $('span[speed="' + channelspeed + '"]', el).css("text-decoration", "underline");
  376.  
  377. let speed = getspeed({
  378. channelname: channelname,
  379. channelspeed: channelspeed,
  380. defspeed: (channeldiv.find('.badge-style-type-verified-artist').length > 0 || channelname.match(/VEVO$/) ? 1 : null),
  381. force1x: (el.closest('#movie_player').find('.ytp-live').length === 1)
  382. });
  383. if($('span[speed="' + speed + '"]', el).click().length === 0) $('video[vsb-video="' + id + '"]')[0].playbackRate = speed;
  384. },500);
  385. }
  386. }
  387. }
  388.  
  389. function ob_youtube_c4player (mutationsList, observer){
  390. for(let mutation of mutationsList) {
  391. if (mutation.attributeName === 'src' && mutation.target.src !== ''){
  392. let id = mutation.target.getAttribute("vsb-video");
  393. let channeldiv = $('#channel-name[vsb-channel="' + id + '"]');
  394. $('video[vsb-video="' + id + '"]')[0].playbackRate = 1;
  395. setTimeout(async function(){
  396. $('video[vsb-video="' + id + '"]')[0].playbackRate = getspeed({
  397. channelname: getchannelname(id, channeldiv),
  398. channelspeed: await GM.getValue(getchannelname(id, channeldiv)),
  399. defspeed: (channeldiv.find('.badge-style-type-verified-artist').length === 1 ? 1 : null)
  400. });
  401. },1000);
  402. }
  403. }
  404. }
  405.  
  406. function youtubefix(){
  407. $('#movie_player[monitored], #c4-player[monitored]').each(
  408. function(){
  409. let video = $('video', this);
  410. if (video.attr('vsb-video') === undefined){
  411. let el = $(this);
  412. let id = el.attr('monitored');
  413. console.log('fixing', video);
  414. setTimeout(function(){
  415. audioboost(video, true);
  416. video.attr('vsb-video', id);
  417. youtubefix2('#vsb-container' + id);
  418. if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) ['timeupdate','seeked', 'pause'].forEach( function(evt) { video[0].addEventListener(evt, changetime,false) });
  419. },750);
  420. } else {
  421. console.log('fixing2', video);
  422. youtubefix2('#vsb-container' + $(this).attr('monitored'));
  423. }
  424. }
  425. );
  426. }
  427.  
  428. function youtubefix2(el, log = false){
  429. try {
  430. $('span:not([title]):visible', el).filter(function() {
  431. return ( this.style.color == COLOR_SELECTED );
  432. }).click();
  433. if (log) console.log('fixing3', video);
  434. } catch {
  435. if (log) console.log('fixing3 failed', video);
  436. }
  437. }
  438.  
  439. function fancyTimeFormat(duration){
  440. var hrs = ~~(duration / 3600);
  441. var mins = ~~((duration % 3600) / 60);
  442. var secs = ~~duration % 60;
  443.  
  444. var ret = "";
  445.  
  446. if (hrs > 0) {
  447. ret += "" + hrs + ":" + (mins < 10 ? "0" : "");
  448. }
  449.  
  450. ret += "" + mins + ":" + (secs < 10 ? "0" : "");
  451. ret += "" + secs;
  452. return ret;
  453. }
  454.  
  455. function changetime (event){
  456. let video = (typeof event.target === "object" ? event.target : event);
  457. if (SHOW_RELATIVE_TIME){
  458. let id = video.getAttribute('vsb-video');
  459. let timediv = $('#movie_player[monitored="' + id + '"]:visible .ytp-time-display:visible');
  460. if (timediv.length === 0) return;
  461. let reltimespan = timediv[0].getElementsByClassName('vsb-reltime');
  462. if (reltimespan.length === 0){
  463. timediv[0].insertAdjacentHTML('beforeend', '<span class="vsb-reltime"></span>');
  464. reltimespan = timediv[0].getElementsByClassName('vsb-reltime');
  465. }
  466. reltimespan[0].innerHTML = (video.playbackRate === 1 || isNaN(video.duration) ? '' : '<span> (</span>' + fancyTimeFormat(video.currentTime / video.playbackRate) + ' / ' + fancyTimeFormat(video.duration / video.playbackRate) + '<span>)</span>');
  467. }
  468. if (SAVE_RESUME_TIME){
  469. const time = Math.floor(video.currentTime),
  470. url = new URL(location);
  471. if (url.pathname === "/watch" && time >= 10){
  472. if (typeof event.target !== "object" || event.type !== "timeupdate" || Number.isInteger(time/5)){
  473. url.searchParams.set('t', (time - 5) + 's');
  474. history.replaceState({}, document.title, url.toString())
  475. }
  476. } else if (url.searchParams.has('t')){
  477. url.searchParams.delete('t');
  478. history.replaceState({}, document.title, url.toString())
  479. }
  480. }
  481. }
  482.  
  483. function audioboost(el = null, force = false){
  484. if (el === null || typeof el !== 'object') el = this;
  485. if (el.tagName !== "VIDEO") return;
  486. if (el.getAttribute('vsb-audioboost') === null){
  487. el.setAttribute('vsb-audioboost', getid());
  488. } else if (!force){
  489. return;
  490. }
  491. const audioCtx = "YTSBC_audioCtx_" + el.getAttribute('vsb-audioboost');
  492. try { window[audioCtx].close(); } catch {}
  493. if (AUDIO_BOOST === 1 && !force) return;
  494. window[audioCtx] = new AudioContext();
  495. const source = window[audioCtx].createMediaElementSource(el),
  496. gainNode = window[audioCtx].createGain();
  497. gainNode.gain.value = AUDIO_BOOST;
  498. source.connect(gainNode);
  499. if (AUDIO_BOOST > 1){
  500. const limiterNode = window[audioCtx].createDynamicsCompressor();
  501. limiterNode.threshold.value = -5.0;
  502. limiterNode.knee.value = 0;
  503. limiterNode.ratio.value = 40.0;
  504. limiterNode.attack.value = 0.001;
  505. limiterNode.release.value = 0.1;
  506.  
  507. limiterNode.connect(window[audioCtx].destination);
  508. gainNode.connect(limiterNode);
  509. } else {
  510. gainNode.connect(window[audioCtx].destination);
  511. }
  512. }
  513.  
  514. function observevideo(el){
  515. const observer = new MutationObserver((mutationsList, observer) => {
  516. for(const mutation of mutationsList) {
  517. if (mutation.target.src === ''){
  518. youtubefix();
  519. } else {
  520. const id = mutation.target.getAttribute('vsb-video');
  521. youtubefix2('#vsb-container' + id);
  522. }
  523. }
  524. });
  525. observer.observe(el, {attributes: true, attributeFilter: ['src']});
  526. }
  527.  
  528. function youtube(){
  529. $('#movie_player:visible:not([monitored]), #c4-player:visible:not([monitored])').each(async function( index ) {
  530. let el = $(this);
  531. let speed, channelspeed;
  532. if (this.id === "movie_player" && !this.classList.contains('ytp-player-minimized')){
  533. let channeldiv = el.closest('ytd-watch-flexy').find('#upload-info #channel-name');
  534. if (!channeldiv.length) channeldiv = $('ytd-watch-metadata #upload-info #channel-name');
  535. if (!channeldiv.length) return;
  536. let channelname = getchannelname(-1, channeldiv);
  537. if (channelname === '') return;
  538. let appendto = (SHOW_ON_PLAYER ? el.find("div.ytp-iv-video-content") : el.find("div.ytp-right-controls"));
  539. if (!appendto.length) return;
  540. let videodiv = el.find('video')
  541. if (!videodiv.length) return;
  542.  
  543. let id = getid();
  544. el.attr('monitored', id);
  545. channeldiv.attr('vsb-channel', id);
  546. videodiv.attr('vsb-video', id);
  547. videodiv.each(audioboost);
  548.  
  549. $('#ytp-id-20 .ytp-menuitem-label:contains(Playback speed)', el).parent().css('display', 'none');
  550.  
  551. console.log("Adding video-id observer");
  552. let el2 = el.closest('ytd-watch-flexy');
  553. if (!el2.length) el2 = $('ytd-watch-flexy:visible');
  554. (new MutationObserver(ob_youtube_movieplayer)).observe(el2[0], { attributes: true });
  555.  
  556. el2 = el.find('video');
  557. if (!el2.length) el2 = $('video:visible');
  558. observevideo(el2[0]);
  559.  
  560. channelspeed = await GM.getValue(channelname);
  561.  
  562. speed = getspeed({
  563. channelname: channelname,
  564. channelspeed: channelspeed,
  565. defspeed: (channeldiv.find('.badge-style-type-verified-artist').length > 0 || channelname.match(/VEVO$/) ? 1 : null),
  566. force1x: (el.find('.ytp-live').length === 1)
  567. });
  568. let div = createcontainer(speed, id);
  569. $('span[speed="' + channelspeed + '"]', div).css("text-decoration", "underline");
  570. if (SHOW_ON_PLAYER){
  571. div.style.position = "absolute";
  572. div.style.zIndex = 10;
  573. div.style.textShadow = "-1px -1px 0 #000, 1px -1px 0 #000, -1px 1px 0 #000, 1px 1px 0 #000";
  574. }
  575. appendto.append(div);
  576. changebuttontitle(id, channelname);
  577. videodiv[0].playbackRate = speed;
  578. if (SHOW_RELATIVE_TIME || SAVE_RESUME_TIME) ['timeupdate','seeked', 'pause'].forEach( function(evt) { videodiv[0].addEventListener(evt, changetime,false) });
  579. } else if (this.id === "c4-player"){
  580. let channeldiv = el.closest('ytd-browse').find('#header #channel-name');
  581. if (!channeldiv.length) return;
  582. let channelname = getchannelname(-1, channeldiv);
  583. if (channelname === '') return;
  584. let videodiv = el.find('video')
  585. if (!videodiv.length) return;
  586.  
  587. el.attr('monitored', id);
  588. let id = getid();
  589. channeldiv.attr('vsb-channel', id);
  590. videodiv.attr('vsb-video', id);
  591. videodiv.each(audioboost);
  592.  
  593. console.log("Adding c4 observer");
  594. (new MutationObserver(ob_youtube_c4player)).observe(el.find('video')[0], { attributes: true, subtree: true });
  595.  
  596. videodiv[0].playbackRate = getspeed({
  597. channelname: channelname,
  598. channelspeed: await GM.getValue(channelname),
  599. defspeed: (channeldiv.find('.badge-style-type-verified-artist').length === 1 ? 1 : null)
  600. });
  601. }
  602. });
  603. if (AUDIO_BOOST !== 1){
  604. $('video:not([vsb-audioboost])').each(audioboost);
  605. }
  606. }
  607.  
  608. function mark_loop(){
  609. if (location.host.endsWith('youtube.com')){
  610. youtube();
  611. let test = document.querySelectorAll('[monitored]');
  612. if (test.length < 2){
  613. setTimeout(mark_loop, ((test.length === 0 || test[0].id !== "movie_player" ? 250 : 2000) * (vis() ? 1 : 2)));
  614. } else {
  615. console.log('stopping loop');
  616. }
  617. } else {
  618. setTimeout(mark_loop, 1500 * (vis() ? 1 : 4));
  619. }
  620. }
  621. if (AUDIO_BOOST !== 1){
  622. mark_loop();
  623. } else {
  624. window.addEventListener('load', mark_loop);
  625. }
  626. })();