YouTube.com audio only mode buttons (Mobile / Desktop)

Adds buttons below video to play media in audio only mode. Works on mobile (m.youtube.com) and on desktop.

  1. // ==UserScript==
  2. // @name YouTube.com audio only mode buttons (Mobile / Desktop)
  3. // @namespace youtube-download-buttons-video-audio
  4. // @version 1.6
  5. // @description Adds buttons below video to play media in audio only mode. Works on mobile (m.youtube.com) and on desktop.
  6. // @author hlorand.hu
  7. // @match https://m.youtube.com/*
  8. // @match https://youtube.com/*
  9. // @match https://*.youtube.com/*
  10. // @icon https://www.google.com/s2/favicons?sz=64&domain=tampermonkey.net
  11. // @grant none
  12. // @license https://creativecommons.org/licenses/by-nc-sa/4.0/
  13. // @run-at document-idle
  14. // ==/UserScript==
  15.  
  16. // Screenshot: https://ibb.co/F7NJZHT
  17.  
  18. (function() {
  19. //'use strict';
  20.  
  21. // itag quality descriptors
  22. // https://gist.github.com/sidneys/7095afe4da4ae58694d128b1034e01e2
  23. function getQualityFromItag(itag) {
  24. const q = {
  25. '5':'FLV 240p',
  26. '6':'FLV 270p',
  27. '17':'3GP 144p',
  28. '18':'MP4 360p',
  29. '22':'MP4 720p',
  30. '34':'FLV 360p',
  31. '35':'FLV 480p',
  32. '36':'3GP 180p',
  33. '37':'MP4 1080p',
  34. '38':'MP4 3072p',
  35. '43':'WebM 360p',
  36. '44':'WebM 480p',
  37. '45':'WebM 720p',
  38. '46':'WebM 1080p',
  39. '82':'MP4 360p (3D)',
  40. '83':'MP4 480p (3D)',
  41. '84':'MP4 720p (3D)',
  42. '85':'MP4 1080p (3D)',
  43. '92':'HLS 240p (3D)',
  44. '93':'HLS 360p (3D)',
  45. '94':'HLS 480p (3D)',
  46. '95':'HLS 720p (3D)',
  47. '96':'HLS 1080p (3D)',
  48. '100':'WebM 360p (3D)',
  49. '101':'WebM 480p (3D)',
  50. '102':'WebM 720p (3D)',
  51. '132':'HLS 240p',
  52. '133':'MP4 240p Video Only',
  53. '134':'MP4 360p Video Only',
  54. '135':'MP4 480p Video Only',
  55. '136':'MP4 720p Video Only',
  56. '137':'MP4 1080p Video Only',
  57. '138':'MP4 2160p60 Video Only',
  58. '139':'M4A Audio Only 48k',
  59. '140':'M4A Audio Only 128k',
  60. '141':'M4A Audio Only 256k',
  61. '151':'HLS 72p',
  62. '160':'MP4 144p Video Only',
  63. '167':'WebM 360p Video Only',
  64. '168':'WebM 480p Video Only',
  65. '169':'WebM 1080p Video Only',
  66. '171':'WebM Audio Only 128k',
  67. '218':'WebM 480p Video Only',
  68. '218':'WebM 144p Video Only',
  69. '242':'WebM 240p Video Only',
  70. '243':'WebM 360p Video Only',
  71. '244':'WebM 480p Video Only',
  72. '245':'WebM 480p Video Only',
  73. '246':'WebM 480p Video Only',
  74. '247':'WebM 720p Video Only',
  75. '248':'WebM 1080p Video Only',
  76. '249':'WebM Audio Only 50k',
  77. '250':'WebM Audio Only 70k',
  78. '251':'WebM Audio Only 160k',
  79. '264':'MP4 1440p Video Only',
  80. '266':'MP4 2160p60 Video Only',
  81. '271':'WebM 1440p Video Only',
  82. '272':'WebM 4320p Video Only',
  83. '278':'WebM 144p Video Only',
  84. '298':'MP4 720p60 Video Only',
  85. '299':'MP4 1080p60 Video Only',
  86. '302':'WebM 720p60 Video Only ',
  87. '303':'WebM 1080p60 Video Only',
  88. '308':'WebM 1440p60 Video Only',
  89. '313':'WebM 2160p Video Only',
  90. '315':'WebM 2160p60 Video Only',
  91. '330':'WebM 144p60 Video Only (hdr)',
  92. '331':'WebM 240p60 Video Only (hdr)',
  93. '332':'WebM 360p60 Video Only (hdr)',
  94. '333':'WebM 480p60 Video Only (hdr)',
  95. '334':'WebM 720p60 Video Only (hdr)',
  96. '335':'WebM 1080p60 Video Only (hdr)',
  97. '336':'WebM 1440p60 Video Only (hdr)',
  98. '337':'WebM 2160p60 Video Only (hdr)'
  99. };
  100. return q[itag] || "Unknown";
  101. }
  102.  
  103. function addbuttons(){
  104. document.getElementById("downloadbuttons").innerText = "";
  105.  
  106. // do not add buttons on homepage
  107. if( !window.location.href.includes("watch") ) return;
  108.  
  109. // if config not available, reload page
  110. if( window.location.href.includes("watch") && ( !ytplayer.config || !ytplayer.config.args ) ){
  111. window.location.reload();
  112. return;
  113. }
  114.  
  115. // get video urls from youtube config
  116. let medias = ytplayer.config.args.raw_player_response.streamingData.adaptiveFormats;
  117. medias.push(...ytplayer.config.args.raw_player_response.streamingData.formats);
  118.  
  119. let urls = [];
  120. for (let media of medias) {
  121.  
  122. // calculate filesizes
  123. if( media.contentLength )
  124. // audio
  125. var _filesize = Math.round(media.contentLength / 1024 / 1024); // byte->MB
  126. else
  127. // video
  128. var _filesize = Math.round(media.approxDurationMs/1000 * media.bitrate/1024/1024/8) // bits/s->MB
  129.  
  130. // collet the most important data to an object
  131. let data = {
  132. url : media.url.replace(/\\u0026/g, '&'),
  133. qualityDescription : getQualityFromItag(media.itag),
  134. qualityLabel : media.qualityLabel,
  135. filesize : _filesize
  136. };
  137.  
  138. // filter videos that has audio+video stream both included or audio only
  139. if( data.qualityDescription.includes("Audio Only") || data.qualityDescription.includes("MP4") && !data.qualityDescription.includes("Only") )
  140. urls.push( data );
  141. }
  142.  
  143. // filter out duplicates
  144. urls = Array.from(new Set(urls.map(a => a.url))).map(url => {
  145. return urls.find(a => a.url === url);
  146. });
  147.  
  148. // create and add buttons
  149. urls.forEach((url)=>{
  150.  
  151. let button = document.createElement('button');
  152. button.setAttribute("url", url.url);
  153. button.textContent = url.qualityDescription.replace(" Only","").replace("WebM","").replace("M4A","") + (isNaN(url.filesize) ? "" : " "+url.filesize+"M");
  154. button.className = "downloadbutton";
  155.  
  156. button.style.margin = "4px";
  157. button.style.padding = "4px";
  158. button.style.position = "relative";
  159. button.style.backgroundColor = "chocolate";
  160.  
  161. button.onclick = function() {
  162. document.write(`<html>
  163. <body>
  164. <video controls><source src=`+this.getAttribute("url")+`></video><br><br>
  165. <style>button{margin-bottom:5px;font-size:large;}video{width:100%;max-height:100vh;}</style>
  166. <button onclick="document.querySelector('video').playbackRate = 3">3</button><br>
  167. <button onclick="document.querySelector('video').playbackRate = 2.75">2.75</button><br>
  168. <button onclick="document.querySelector('video').playbackRate = 2.5">2.5</button><br>
  169. <button onclick="document.querySelector('video').playbackRate = 2.25">2.25</button><br>
  170. <button onclick="document.querySelector('video').playbackRate = 2">2</button><br>
  171. <button onclick="document.querySelector('video').playbackRate = 1.75">1.75</button><br>
  172. <button onclick="document.querySelector('video').playbackRate = 1.5">1.5</button><br>
  173. <button onclick="document.querySelector('video').playbackRate = 1">1</button><br>
  174. </body>
  175. </html>`);
  176. };
  177.  
  178. let target = document.getElementById('downloadbuttons');
  179. target.insertBefore(button, target.firstChild);
  180.  
  181. }); // end foreach
  182.  
  183. } // end addbuttons
  184.  
  185.  
  186. // Periodically check if the buttons are visible (sometimes YouTube redraws its interface).
  187. setInterval(()=>{
  188.  
  189. // Creating a div that will contain buttons.
  190. if( document.getElementById("downloadbuttons") == undefined ){
  191.  
  192. // placement of buttons on desktop
  193. let parent = document.getElementById('above-the-fold');
  194.  
  195. // placement of buttons on tablet
  196. if( !parent ){
  197. parent = document.querySelector('.watch-below-the-player');
  198. }
  199.  
  200. // placement of buttons on mobile
  201. if( !parent ){
  202. parent = document.querySelector('.related-chips-slot-wrapper');
  203. }
  204.  
  205. if( parent ){
  206. let wrapper = document.createElement('div');
  207. wrapper.setAttribute("id","downloadbuttons");
  208. parent.insertBefore(wrapper, parent.firstChild);
  209. addbuttons();
  210. }
  211.  
  212. }
  213.  
  214. // Sometimes the buttons are not added, so I check and add them if necessary.
  215. if( document.getElementById("downloadbuttons") && document.getElementById("downloadbuttons").textContent.trim() === '' ){
  216. addbuttons();
  217. }
  218. }, 1000);
  219.  
  220. })();