Deezer PiP - deezer.com

Adds PiP features to Deezer

  1. // ==UserScript==
  2. // @name Deezer PiP - deezer.com
  3. // @namespace https://github.com/Thibb1
  4. // @match https://www.deezer.com/*
  5. // @grant none
  6. // @version 1.0.1
  7. // @author Thibb1
  8. // @description Adds PiP features to Deezer
  9. // @license GPL
  10. // ==/UserScript==
  11.  
  12. let timer;
  13. function debounce(func) {
  14. return (...args) => {
  15. clearTimeout(timer);
  16. timer = setTimeout(() => { func.apply(this, args); }, 1000);
  17. };
  18. }
  19. const CLICK_TIMEOUT = 200;
  20. const NEXT = '.player-controls [data-testid="StepForwardIcon"]';
  21. const PREVIOUS = '.player-controls [data-testid="StepBackwardIcon"]';
  22. const PAUSE = '.player-controls [data-testid="PauseIcon"]'
  23. const PLAY = '.player-controls [data-testid="PlayIcon"]'
  24. const QUEUELIST = '.player-full.player-lyrics-full';
  25. const QUEUE = '.player-options > ul > li:nth-child(2) > button';
  26. const FAVORITES = ".sidebar-nav-list > li:nth-child(5) > a";
  27. const SHUFFLE = '[data-testid="user-shuffle-button"]';
  28. const MIX = '[data-testid="NoteWaveIcon"]';
  29. const REMOVE_FAV = '.player-bottom [data-testid="HeartFillIcon"]'
  30. const ADD_FAV = '.player-bottom [data-testid="HeartIcon"]'
  31.  
  32. const STYLE_TEXT = `
  33. body {
  34. background: #212121;
  35. color: #fff;
  36. font-weight: 700;
  37. }
  38. button {
  39. flex: 1;
  40. height: 45px;
  41. font-size: 16px;
  42. box-shadow: 0px 0px 20px -20px;
  43. border-radius: 5px;
  44. cursor: pointer;
  45. transition: all 0.2s ease-in-out 0ms;
  46. user-select: none;
  47. }
  48. button:hove {
  49. box-shadow: 0px 0px 20px -18px;
  50. }
  51. button:active {
  52. transform: scale(0.95);
  53. }
  54. .shuffle {
  55. background: #ca2a36;
  56. color: #fff;
  57. }
  58. .main > div {
  59. flex: 1;
  60. }
  61. .main {
  62. flex-direction: column;
  63. width: 90%;
  64. }
  65. body, p, div {
  66. display: flex;
  67. gap: 8px;
  68. justify-content: center;
  69. }
  70. `;
  71.  
  72.  
  73. function getTitle() {
  74. return [...document.querySelectorAll('.track-link')].map((e) => e.textContent).join(' - ');
  75. }
  76.  
  77. function createButton(text, onClick) {
  78. const button = document.createElement('button');
  79. button.textContent = text;
  80. button.addEventListener('click', onClick);
  81. return button;
  82. }
  83.  
  84. async function onClick() {
  85. const pipWindow = await documentPictureInPicture.requestWindow({ width: 284, height: 284 });
  86. const main = pipWindow.document.createElement('div');
  87. const audioController = document.createElement("div");
  88. const nextSong = createButton('Next', () => {
  89. document.querySelector(NEXT).parentElement.click();
  90. });
  91. const previousSong = createButton('Previous', () => {
  92. document.querySelector(PREVIOUS).parentElement.click();
  93. });
  94. const playPause = createButton('', () => {
  95. const play = document.querySelector(PLAY);
  96. const pause = document.querySelector(PAUSE);
  97. if (play) {
  98. play.parentElement.click();
  99. playPause.textContent = 'Pause';
  100. } else {
  101. pause.parentElement.click();
  102. playPause.textContent = 'Play';
  103. }
  104. });
  105. const playMix = createButton('Launch Track mix', () => {
  106. const queueOpened = document.querySelector(QUEUELIST);
  107. if (!queueOpened) {
  108. document.querySelector(QUEUE).click();
  109. }
  110. setTimeout(() => {
  111. document.querySelector(`${QUEUELIST} [aria-selected=true] [aria-haspopup]`).click();
  112. document.querySelector(MIX).closest('button').click();
  113. if (!queueOpened) {
  114. document.querySelector(QUEUE).click();
  115. }
  116. }, CLICK_TIMEOUT);
  117. });
  118. const shuffle = createButton('Shuffle my music', () => {
  119. document.querySelector(FAVORITES).click();
  120. setTimeout(() => {
  121. document.querySelector(SHUFFLE).click();
  122. }, CLICK_TIMEOUT);
  123. });
  124. const toggleFav = createButton('', () => {
  125. const fav = document.querySelector(ADD_FAV);
  126. if (fav) {
  127. fav.parentElement.click();
  128. toggleFav.textContent = 'Remove from favorites';
  129. } else {
  130. document.querySelector(REMOVE_FAV).parentElement.click();
  131. toggleFav.textContent = 'Add to favorites';
  132. }
  133. });
  134. const songTitle = pipWindow.document.createElement('p');
  135. const style = pipWindow.document.createElement('style');
  136. main.classList.add('main');
  137. shuffle.classList.add('shuffle');
  138. const getText = () => {
  139. songTitle.textContent = getTitle();
  140. playPause.textContent = document.querySelector(PLAY) ? 'Play' : 'Pause';
  141. toggleFav.textContent = document.querySelector(REMOVE_FAV) ? 'Remove from favorites' : 'Add to favorites';
  142. }
  143. getText();
  144. audioController.appendChild(previousSong);
  145. audioController.appendChild(playPause);
  146. audioController.appendChild(nextSong);
  147. main.appendChild(audioController);
  148. main.appendChild(toggleFav);
  149. main.appendChild(playMix);
  150. main.appendChild(shuffle);
  151. main.appendChild(songTitle);
  152. pipWindow.document.body.appendChild(main);
  153. style.innerHTML = STYLE_TEXT;
  154. pipWindow.document.head.appendChild(style);
  155. const observer = new MutationObserver(() => getText());
  156. observer.observe(document.body, {
  157. childList: true,
  158. subtree: true,
  159. });
  160. }
  161.  
  162. const observer = new MutationObserver((mutations) => {
  163. debounce(() => {
  164. observer.disconnect();
  165. const pipButton = document.createElement('button');
  166. pipButton.textContent = 'PiP';
  167. pipButton.addEventListener('click', onClick);
  168. pipButton.classList.add('svg-icon-group-btn');
  169. document.querySelector('.player-options > ul > li:nth-child(1) > ul').appendChild(pipButton);
  170. })();
  171. });
  172.  
  173. window.onload = () => {
  174. observer.observe(document.body, {
  175. childList: true,
  176. subtree: true,
  177. });
  178. };