Youtube Audio Mode

Listen to only the audio on YouTube without loading the video.

  1. // ==UserScript==
  2. // @name Youtube Audio Mode
  3. // @description Listen to only the audio on YouTube without loading the video.
  4. // @version 0.2.8
  5. // @author Burn
  6. // @namespace https://openuserjs.org/users/burn
  7. // @copyright 2022, burn (https://openuserjs.org/users/burn)
  8. // @include https://www.youtube.com/*
  9. // @match https://www.youtube.com/*
  10. // @license MIT
  11. // @run-at document-end
  12. // @grant GM.setValue
  13. // @grant GM.getValue
  14. // @noframes
  15. // ==/UserScript==
  16.  
  17. /*
  18.  
  19. PLEASE NOTE
  20.  
  21. The original version of this userscript is written by Teaqu
  22. and it's published here: https://github.com/teaqu/youtube-audio-mode
  23.  
  24. */
  25.  
  26. (async function(open, originalFetch) {
  27. const DBG = false;
  28. let myLog = (msg) => {
  29. DBG && console.log(GM_info.script.name + " | " + msg);
  30. };
  31.  
  32. window.addEventListener("yt-navigate-finish", audioMode);
  33. window.onYouTubeIframeAPIReady = await audioMode();
  34.  
  35. async function audioMode() {
  36. if (location.pathname == "/watch") {
  37. let video = document.getElementsByTagName("video")[0];
  38. let audioMode = await GM.getValue("ytAudioMode");
  39. await addToMenu(audioMode);
  40. if (audioMode) {
  41. setPoster(video, ["maxres", "hq", "sd"]);
  42. watchFetchStream(video);
  43. } else { myLog("audiomode is disabled"); }
  44. }
  45. }
  46.  
  47. function watchFetchStream(video) {
  48. const constantMock = originalFetch;
  49. unsafeWindow.fetch = function() {
  50. return new Promise((resolve, reject) => {
  51. constantMock.apply(this, arguments)
  52. .then((response) => {
  53. if (response.url.indexOf("mime=audio") > -1) { // && response.type != "cors"){
  54. !video.paused && video.pause();
  55. myLog("current time after pausing video: " + video.currentTime);
  56. let currentTimeStamp = video.currentTime;
  57. video.src = response.url.split("&range")[0];
  58. video.currentTime = currentTimeStamp;
  59. myLog("current time before resuming video playback: " + video.currentTime);
  60. video.paused && video.play();
  61. }
  62. resolve(response);
  63. })
  64. .catch((error) => {
  65. reject(response);
  66. })
  67. });
  68. }
  69. }
  70.  
  71. // Add audio mode to the settings menu
  72. async function addToMenu(audioMode) {
  73. let panel = document.getElementsByClassName("ytp-panel-menu")[0];
  74. if (!panel.innerHTML.includes("Audio Mode")) {
  75. panel.innerHTML += `
  76. <div class="ytp-menuitem"
  77. aria-checked="${audioMode}"
  78. id="audio-mode">
  79. <div class="ytp-menuitem-icon"></div>
  80. <div class="ytp-menuitem-label">Audio Mode</div>
  81. <div class="ytp-menuitem-content">
  82. <div class="ytp-menuitem-toggle-checkbox">
  83. </div>
  84. </div>`;
  85.  
  86. // Toggle audio mode on or off
  87. let audioToggle = document.getElementById("audio-mode");
  88. let videoElm = document.getElementsByTagName("video")[0];
  89. audioToggle.onclick = async function() {
  90. let audioMode = ! await GM.getValue("ytAudioMode");
  91. this.setAttribute("aria-checked", audioMode);
  92. GM.setValue("ytAudioMode", audioMode);
  93. location.href = location.protocol
  94. + "//" + location.hostname
  95. + location.pathname
  96. + setGetVar("t", parseInt(videoElm.currentTime) + "s");
  97. }
  98. }
  99. }
  100.  
  101. // Set the video poster from thumbnails with the best avaliable format
  102. // https://developers.google.com/youtube/v3/docs/thumbnails
  103. async function setPoster(video, fmts) {
  104. let img = new Image();
  105. let videoId = location.search.match(/v=(.+?)(&|$)/)[1];
  106. img.src = `//i.ytimg.com/vi/${videoId}/${fmts.shift()}default.jpg`
  107. img.onload = function() {
  108. myLog("thumbnail loaded");
  109. // A height 90 is YouTube "not found" image.
  110. if (img.height <= 90) {
  111. myLog("thumbnail should be youtube not found image");
  112. setPoster(video, fmts);
  113. } else {
  114. myLog("thumbnail found, now applying css rule to display it");
  115. video.style.background = `url(${img.src}) no-repeat center`;
  116. video.style.backgroundSize = "contain";
  117. }
  118. };
  119. }
  120.  
  121. function setGetVar(name, value) {
  122. let vars = getVars();
  123. vars[name] = value;
  124. let strSearch = "?";
  125. for (let n in vars)
  126. strSearch += n + "=" + vars[n] + "&";
  127. return strSearch.substring(0, strSearch.length - 1);
  128. }
  129. function getVars() {
  130. // https://stackoverflow.com/questions/12049620/how-to-get-get-variables-value-in-javascript#12049703
  131. let GET = [];
  132. window.location.href.replace(/[?&]+([^=&]+)=([^&]*)/gi, function(a,name,value) {GET[name] = value;});
  133. return GET;
  134. }
  135. })(XMLHttpRequest.prototype.open, window.fetch || unsafeWindow.fetch);