Youtube Audio Mode

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

当前为 2022-07-05 提交的版本,查看 最新版本

  1. // ==UserScript==
  2. // @name Youtube Audio Mode
  3. // @description Listen to only the audio on YouTube without loading the video.
  4. // @version 0.2.4
  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. I've found the original userscript on Github: https://github.com/teaqu/youtube-audio-mode
  22. but it's not working anymore so I've opened an issue. After a month without receiving a reply
  23. I've decided to update the script myself.
  24.  
  25. */
  26.  
  27. (async function(open, originalFetch) {
  28. const DBG = false;
  29. let myLog = (msg) => {
  30. DBG && console.log(GM_info.script.name + " | " + msg);
  31. };
  32.  
  33. window.addEventListener("yt-navigate-finish", audioMode);
  34. window.onYouTubeIframeAPIReady = await audioMode();
  35.  
  36. async function audioMode() {
  37. if (location.pathname == "/watch") {
  38. let video = document.getElementsByTagName("video")[0];
  39. let audioMode = await GM.getValue("ytAudioMode");
  40. await addToMenu(audioMode);
  41. if (audioMode) {
  42. setPoster(video, ["maxres", "hq", "sd"]);
  43. watchFetchStream(video);
  44. } else {myLog("audiomode disabled");}
  45. }
  46. }
  47.  
  48. function watchFetchStream(video) {
  49. const constantMock = originalFetch;
  50. unsafeWindow.fetch = function() {
  51. return new Promise((resolve, reject) => {
  52. constantMock.apply(this, arguments)
  53. .then((response) => {
  54. if(response.url.indexOf("mime=audio") > -1) { // && response.type != "cors"){
  55. video.pause();
  56. video.src = response.url.split("&range")[0];
  57. video.play();
  58. }
  59. resolve(response);
  60. })
  61. .catch((error) => {
  62. reject(response);
  63. })
  64. });
  65. }
  66. }
  67.  
  68. // Add audio mode to the settings menu
  69. async function addToMenu(audioMode) {
  70. let panel = document.getElementsByClassName("ytp-panel-menu")[0];
  71. if (!panel.innerHTML.includes("Audio Mode")) {
  72. panel.innerHTML += `
  73. <div class="ytp-menuitem"
  74. aria-checked="${audioMode}"
  75. id="audio-mode">
  76. <div class="ytp-menuitem-icon"></div>
  77. <div class="ytp-menuitem-label">Audio Mode</div>
  78. <div class="ytp-menuitem-content">
  79. <div class="ytp-menuitem-toggle-checkbox">
  80. </div>
  81. </div>`;
  82.  
  83. // Toggle audio mode on or off
  84. let audioToggle = document.getElementById("audio-mode");
  85. audioToggle.onclick = async function() {
  86. let audioMode = ! await GM.getValue("ytAudioMode");
  87. this.setAttribute("aria-checked", audioMode);
  88. GM.setValue("ytAudioMode", audioMode);
  89. location.reload();
  90. }
  91. }
  92. }
  93.  
  94. // Set the video poster from thumbnails with the best avaliable format
  95. // https://developers.google.com/youtube/v3/docs/thumbnails
  96. async function setPoster(video, fmts) {
  97. let img = new Image();
  98. let videoId = location.search.match(/v=(.+?)(&|$)/)[1];
  99. img.src = `//i.ytimg.com/vi/${videoId}/${fmts.shift()}default.jpg`
  100. img.onload = function() {
  101. // A height 90 is YouTube"s not found image.
  102. if (img.height <= 90) {
  103. setPoster(video, fmts);
  104. } else {
  105. video.style.background = `url(${img.src}) no-repeat center`;
  106. video.style.backgroundSize = "contain";
  107. }
  108. };
  109. }
  110. })(XMLHttpRequest.prototype.open, window.fetch || unsafeWindow.fetch);