HTML5 notifications on Spotify Web Player

Adds silent song notifications with title, artist and cover art

  1. // ==UserScript==
  2. // @name HTML5 notifications on Spotify Web Player
  3. // @description Adds silent song notifications with title, artist and cover art
  4. // @namespace https://greasyfork.org/users/4813-swyter
  5. // @match https://play.spotify.com/*
  6. // @version 2016.06.08
  7. // @grant none
  8. // @noframes
  9. // ==/UserScript==
  10.  
  11. /* run this just on the parent page, not in sub-frames */
  12. if (window.parent !== window)
  13. throw "stop execution";
  14.  
  15. function when_external_loaded()
  16. {
  17. /* request permission to show notifications, if needed */
  18. if (Notification.permission !== 'granted')
  19. Notification.requestPermission();
  20.  
  21. /* create a listener mechanism for title changes using mutation observers,
  22. let's be good citizens (http://stackoverflow.com/a/29540461) */
  23. document.aptEventListener = document.addEventListener;
  24. document.addEventListener = function(what, callback)
  25. {
  26. if (what !== 'title')
  27. return document.aptEventListener.apply(this, arguments);
  28.  
  29. console.log('setting title event listener =>', arguments);
  30.  
  31. new MutationObserver(function(mutations)
  32. {
  33. console.log('mutation observer triggered =>', mutations[0].target);
  34. /* save the title also at trigger time, so that we can skip it if the song
  35. has quickly changed between the triggering and the notificaction shows up */
  36. setTimeout(
  37. callback.bind(this, document.title),
  38. 4500
  39. );
  40.  
  41. }).observe(document.querySelector('title'), {subtree: true, childList: true, characterData: true});
  42. };
  43.  
  44. /* trigger a new notification every time the page title changes */
  45. document.addEventListener('title', function(title_when_triggered)
  46. {
  47. /* trigger it only if we are actually playing songs and we haven't
  48. shown a notification for this song already */
  49. if (!document.title.match('▶') || document.prevtitle === document.title || title_when_triggered !== document.title)
  50. {
  51. console.log('returning without showing it up =>', document.title);
  52. return;
  53. }
  54.  
  55. /* save the current title to avoid needless repetition */
  56. document.prevtitle = document.title;
  57.  
  58. /* some debug printing to help out development, seems work work pretty nicely */
  59. console.log('this seems to be a new song, showing it up =>', document.title);
  60.  
  61. /* feel free to customize the formatting and layout to your liking */
  62. var track_name = document.querySelector("iframe#app-player").contentWindow.document.querySelector("#track-name > a").textContent;
  63. var track_artist = document.querySelector("iframe#app-player").contentWindow.document.querySelector("#track-artist > a").textContent;
  64. var track_coverart = document.querySelector("iframe#app-player").contentWindow.document.querySelector(".sp-image-img").style.backgroundImage.replace(/"/g,'').split("(")[1].split(")")[0];
  65.  
  66. /* show it! */
  67. new Notification(track_name, {body: track_artist, icon: track_coverart, silent: true});
  68. });
  69. }
  70.  
  71. /* inject this cleaning function right in the page to avoid silly sandbox-related greasemonkey limitations */
  72. window.document.head.appendChild(
  73. inject_fn = document.createElement("script")
  74. );
  75.  
  76. inject_fn.innerHTML = '(' + when_external_loaded.toString() + ')()';