An easy way to have a 24/7 audio stream of music.
at rita-moe 7.5 kB view raw
1<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0"> 2 <xsl:output method="html" 3 doctype-system="about:legacy-compat" 4 encoding="UTF-8" /> 5 <xsl:template match="/icestats"> 6 <html xmlns="http://www.w3.org/1999/xhtml"> 7 <head> 8 <meta charset="utf-8" /> 9 <meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" /> 10 <title>RRM - radio.rita.moe</title> 11 <link rel="icon" href="https://rita.moe/rita-icon.png" /> 12 <link rel="stylesheet" type="text/css" href="style-status.css" /> 13 <link rel="stylesheet" href="https://cdn.plyr.io/3.7.8/plyr.css" /> 14 <script src="https://cdn.plyr.io/3.7.8/plyr.polyfilled.js"></script> 15 </head> 16 <body> 17 <div class="content"> 18 <h1 id="header">radio.rita.moe</h1> 19 <!--mount point stats--> 20 <xsl:for-each select="source"> 21 <xsl:choose> 22 <xsl:when test="listeners"> 23 <div class="roundbox" data-mount="{@mount}"> 24 <div class="mounthead"> 25 <h3 class="mount"> 26 <xsl:value-of select="server_name" /> 27 <small>(<xsl:value-of select="@mount" />)</small> 28 </h3> 29 <div class="right"> 30 <xsl:choose> 31 <xsl:when test="authenticator"> 32 <a class="auth" href="/auth.xsl">Login</a> 33 </xsl:when> 34 <xsl:otherwise> 35 <ul class="mountlist"> 36 <li> 37 <a class="play" href="{@mount}.m3u">M3U</a> 38 </li> 39 </ul> 40 </xsl:otherwise> 41 </xsl:choose> 42 </div> 43 </div> 44 <div class="mountcont"> 45 <xsl:if test="server_type"> 46 <div class="audioplayer"> 47 <audio controls="controls" preload="none"> 48 <source src="{@mount}" type="{server_type}" /> 49 </audio> 50 </div> 51 </xsl:if> 52 <div class="playing"> 53 <xsl:if test="artist"> 54 <xsl:value-of select="artist" /> 55 - 56 </xsl:if> 57 <xsl:value-of select="title" /> 58 </div> 59 </div> 60 </div> 61 </xsl:when> 62 <xsl:otherwise> 63 <h3> 64 <xsl:value-of select="@mount" /> 65 - Not Connected 66 </h3> 67 </xsl:otherwise> 68 </xsl:choose> 69 </xsl:for-each> 70 <div id="footer"> 71 Powered by <a href="https://www.icecast.org">Icecast</a> 72 and <a href="https://www.liquidsoap.info">Liquidsoap</a>. 73 </div> 74 </div> 75 <script type="text/javascript"> 76<![CDATA[ 77 // We'll store the last title of the current mount point to check for changes 78 let lastTitle = '' 79 // Store the current mount point 80 let currentMount = '' 81 82 // On every audio element, create a new Plyr instance 83 document.querySelectorAll('div[data-mount]').forEach((e) => { 84 const plyr = new Plyr( 85 e.querySelector('audio'), 86 { 87 controls: ['play', 'current-time', 'mute', 'volume'], 88 invertTime: false, 89 toggleInvert: false, 90 } 91 ) 92 93 // When we start playing, store current mount point, clear last title and stop all other players 94 plyr.on( 95 'play', 96 (_) => { 97 currentMount = e.dataset.mount 98 lastTitle = '' 99 100 document.querySelectorAll('[data-mount]').forEach((a) => { 101 if (a !== e) { 102 a.querySelector('audio').stop() 103 } 104 }) 105 } 106 ) 107 108 // When we pause, clear current mount point and stop all data 109 plyr.on( 110 'pause', 111 (_) => { 112 // To prevent overlaps, we only clear the current mount if it's the same as the one we're pausing 113 if (currentMount === e.dataset.mount) { 114 currentMount = '' 115 } 116 117 plyr.stop() 118 } 119 ) 120 }) 121 122 function np (wait) { 123 // Find all mount points and fetch their status 124 document.querySelectorAll('div[data-mount]').forEach((e) => { 125 fetch('./status-json.xsl?mount=' + e.dataset.mount) 126 .then((r) => r.json()) 127 .then((j) => { 128 // We delay the update to match the usual delay of the audio stream 129 setTimeout((_) => { 130 // The title may not be present, so we check for it 131 if (j.icestats.source.title) { 132 // Update the now playing text 133 e.querySelector('.playing').innerHTML = j.icestats.source.title 134 135 // If the title has changed and this is our current playing mount 136 if (lastTitle !== j.icestats.source.title && currentMount === e.dataset.mount) { 137 // Update the last title 138 lastTitle = j.icestats.source.title 139 140 // If we have permission, send a notification 141 if ('Notification' in window && Notification.permission === 'granted') { 142 const lastNotification = new Notification( 143 'RRM - Now Playing', 144 { 145 body: j.icestats.source.title, 146 icon: 'https://rita.moe/rita-icon.png', 147 renotify: true, 148 requireInteraction: true, 149 silent: true, 150 tag: 'now-playing', 151 } 152 ) 153 154 // Close the notification after 10 seconds, as renotify 155 // isn't working perfectly when the track changes 156 setTimeout( 157 (_) => { 158 lastNotification.close() 159 }, 160 10000 161 ) 162 } 163 } 164 } else { 165 // If the title is not present, clear the now playing text 166 e.querySelector('.playing').innerHTML = '' 167 } 168 }, wait ?? 3000) 169 }) 170 }) 171 } 172 173 // Enable interval to fetch now playing data 174 setInterval(np, 5000) 175 // And run it immediately on page load 176 np(0) 177 178 // Request notification permission if it's still default 179 if ('Notification' in window && Notification.permission === 'default') { 180 Notification.requestPermission() 181 } 182]]> 183 </script> 184 </body> 185 </html> 186 </xsl:template> 187</xsl:stylesheet>