An easy way to have a 24/7 audio stream of music.

Rita.moe's status.xsl and css

+3
docker-compose.yml
···
- ICECAST_RELAY_PASSWORD
- ICECAST_HOSTNAME
- ICECAST_MAX_SOURCES
liquidsoap:
image: "phasecorex/liquidsoap"
···
- ICECAST_RELAY_PASSWORD
- ICECAST_HOSTNAME
- ICECAST_MAX_SOURCES
+
volumes:
+
- ./status.xsl:/usr/share/icecast/web/status.xsl:ro
+
- ./style-status.css:/usr/share/icecast/web/style-status.css:ro
liquidsoap:
image: "phasecorex/liquidsoap"
+110
status.xsl
···
···
+
<xsl:stylesheet xmlns:xsl="http://www.w3.org/1999/XSL/Transform" version="1.0">
+
<xsl:output method="html"
+
doctype-system="about:legacy-compat"
+
encoding="UTF-8" />
+
<xsl:template match="/icestats">
+
<html xmlns="http://www.w3.org/1999/xhtml">
+
<head>
+
<meta charset="utf-8" />
+
<meta name="viewport" content="width=device-width, initial-scale=1.0, user-scalable=yes" />
+
<title>radio.rita.moe</title>
+
<link rel="icon" href="https://rita.moe/rita-icon.png" />
+
<link rel="stylesheet" type="text/css" href="style-status.css" />
+
<link rel="stylesheet" href="https://cdn.plyr.io/3.5.8/plyr.css" />
+
<script src="https://cdn.plyr.io/3.5.8/plyr.polyfilled.js"></script>
+
</head>
+
<body>
+
<div class="content">
+
<h1 id="header">radio.rita.moe</h1>
+
<!--mount point stats-->
+
<xsl:for-each select="source">
+
<xsl:choose>
+
<xsl:when test="listeners">
+
<div class="roundbox">
+
<div class="mounthead">
+
<h3 class="mount">
+
<xsl:value-of select="server_name" />
+
<small>(<xsl:value-of select="@mount" />)</small>
+
</h3>
+
<div class="right">
+
<xsl:choose>
+
<xsl:when test="authenticator">
+
<a class="auth" href="/auth.xsl">Login</a>
+
</xsl:when>
+
<xsl:otherwise>
+
<ul class="mountlist">
+
<li>
+
<a class="play" href="{@mount}.m3u">M3U</a>
+
</li>
+
<!--
+
<li>
+
<a class="play" href="{@mount}.xspf">XSPF</a>
+
</li>
+
<li>
+
<a class="play" href="{@mount}.vclt">VCLT</a>
+
</li>
+
-->
+
</ul>
+
</xsl:otherwise>
+
</xsl:choose>
+
</div>
+
</div>
+
<div class="mountcont">
+
<xsl:if test="server_type">
+
<div class="audioplayer">
+
<audio controls="controls" preload="none">
+
<source src="{@mount}" type="{server_type}" />
+
</audio>
+
</div>
+
</xsl:if>
+
<div class="playing">
+
<xsl:if test="artist">
+
<xsl:value-of select="artist" />
+
-
+
</xsl:if>
+
<xsl:value-of select="title" />
+
</div>
+
</div>
+
</div>
+
</xsl:when>
+
<xsl:otherwise>
+
<h3>
+
<xsl:value-of select="@mount" />
+
- Not Connected
+
</h3>
+
</xsl:otherwise>
+
</xsl:choose>
+
</xsl:for-each>
+
<div id="footer">
+
Powered by <a href="https://www.icecast.org">Icecast</a>
+
and <a href="https://www.liquidsoap.info">Liquidsoap</a>.
+
</div>
+
</div>
+
<script type="text/javascript">
+
<![CDATA[
+
function s () {
+
fetch('https://radio.rita.moe/status-json.xsl')
+
.then(r => r.json())
+
.then(j => {
+
setTimeout(_ => {
+
document.querySelector('.playing').innerHTML = j.icestats.source.title
+
}, 5000)
+
})
+
}
+
+
setInterval(s, 3000)
+
+
document.querySelectorAll('audio').forEach(e => {
+
new Plyr(e, {
+
controls: ['play', 'current-time', 'mute', 'volume'],
+
invertTime: false,
+
toggleInvert: false
+
})
+
})
+
]]>
+
</script>
+
</body>
+
</html>
+
</xsl:template>
+
</xsl:stylesheet>
+
+158
style-status.css
···
···
+
html {
+
margin: 0;
+
padding: 0;
+
}
+
+
body {
+
align-items: center;
+
background: #DF8AAC;
+
background: -webkit-linear-gradient(135deg, #DF8AAC, #B63A74);
+
background: linear-gradient(135deg, #DF8AAC, #B63A74);
+
display: flex;
+
justify-content: center;
+
margin: 0;
+
min-height: 100vh;
+
}
+
+
.content {
+
background-color: rgba(255, 255, 255, 0.8);
+
font-family: Arial, Helvetica, sans-serif;
+
font-size: 0.8rem;
+
max-width: 420px;
+
padding: 1rem 2rem;
+
text-align: center;
+
width: 100%;
+
}
+
+
a {
+
border-bottom: 1px dotted #0074D9;
+
color: #0074D9;
+
text-decoration: none;
+
}
+
+
a:hover {
+
border-bottom: 1px solid #0074D9;
+
color: #0074D9;
+
text-decoration: none;
+
}
+
+
h1 {
+
background: transparent url(https://rita.moe/rita-icon.png) no-repeat scroll left center;
+
background-size: contain;
+
font-family: Verdana, sans-serif;
+
font-size: 3em;
+
font-weight: bold;
+
margin-top: 3px;
+
padding: 10px 0px 10px 60px;
+
text-decoration: none;
+
}
+
+
h3 {
+
font-family: Verdana, sans-serif;
+
font-size: 1.5em;
+
font-weight: bold;
+
margin: 0px;
+
padding: 0px;
+
}
+
+
h3 small {
+
color: #AAA;
+
font-size: 70%;
+
padding-left: 5px;
+
}
+
+
.roundbox {
+
background-color: #fff;
+
border-radius: 10px;
+
margin-bottom: 35px;
+
padding: 15px 20px;
+
}
+
+
.roundbox h3 {
+
border-bottom: 1px groove #ACACAC;
+
margin-bottom: 10px;
+
}
+
+
.right {
+
float: right;
+
}
+
+
.mounthead h3 {
+
border-bottom: none;
+
float: left;
+
margin-bottom: 0px;
+
}
+
+
.mountcont {
+
border-top: 1px groove #ACACAC;
+
clear: both;
+
}
+
+
ul.mountlist {
+
list-style: none;
+
margin: 0;
+
padding: 0;
+
text-align: right;
+
}
+
+
.mountlist li {
+
display: inline;
+
}
+
+
a.play {
+
background: transparent url(/tunein.png) no-repeat scroll left center;
+
background-size: auto 100%;
+
border: none;
+
margin-left: 25px;
+
padding-left: 22px;
+
}
+
+
a.auth {
+
background: transparent url(/key.png) no-repeat scroll left top;
+
background-size: auto 100%;
+
border: none;
+
margin-left: 25px;
+
padding-left: 22px;
+
}
+
+
.audioplayer {
+
margin-top: 5px;
+
}
+
+
.audioplayer .plyr__controls {
+
justify-content: center;
+
}
+
+
.audioplayer .plyr__controls .plyr__controls__item:first-child {
+
margin-right: 0;
+
}
+
+
#footer {
+
border-top: 1px groove #ACACAC;
+
font-size: 80%;
+
margin-top: 20px;
+
}
+
+
@media screen and (max-width: 520px) {
+
body {
+
display: block;
+
min-height: calc(100vh - 2rem);
+
padding: 1rem;
+
}
+
+
.content {
+
max-width: none;
+
padding: 1rem;
+
width: auto;
+
}
+
+
h1 {
+
font-size: 1.5em;
+
padding: 10px 0px 10px 50px;
+
text-align: left;
+
}
+
+
a.play {
+
margin-left: 15px;
+
}
+
}