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>