You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
350 lines
10 KiB
350 lines
10 KiB
<!-- If you're reading this...this was a weekend project...please don't judge -->
|
|
<!DOCTYPE HTML>
|
|
<html>
|
|
<head>
|
|
<title>radio.jerryaldrichiii.com</title>
|
|
<meta charset="utf-8">
|
|
<meta name="viewport" content="width=device-width,initial-scale=1.0">
|
|
<meta name="Description" content="radio.jerryaldrichiii.com"><Paste>
|
|
<style>
|
|
* {
|
|
background: black;
|
|
background-color: black;
|
|
}
|
|
a, a:hover, a:active, a:visited {
|
|
color: #00FF00;
|
|
}
|
|
body {
|
|
background: black;
|
|
background-color: black;
|
|
color: #00FF00;
|
|
text-align: center;
|
|
overflow-y: scroll;
|
|
display: inline;
|
|
font: calc(0.40em + 1vmin) monospace;
|
|
}
|
|
#stream-info {
|
|
width: 60ch;
|
|
margin: auto;
|
|
margin-bottom: 1ch;
|
|
}
|
|
#controls {
|
|
display: inline-flex;
|
|
justify-content: space-between;
|
|
width: 60ch;
|
|
}
|
|
button {
|
|
background-color: black;
|
|
color: #00FF00;
|
|
border: none;
|
|
cursor: pointer;
|
|
outline: none;
|
|
padding: 0px 0px 0px 0px;
|
|
font-size: inherit;
|
|
}
|
|
#volume-container > * {
|
|
vertical-align: middle;
|
|
}
|
|
#volume {
|
|
-webkit-appearance: none;
|
|
-webkit-transition: .2s;
|
|
background: black;
|
|
outline: auto;
|
|
outline-color: #00FF00;
|
|
opacity: 0.7;
|
|
transition: opacity .2s;
|
|
vertical-align: middle;
|
|
}
|
|
#volume:hover {
|
|
opacity: 1;
|
|
}
|
|
#volume::-webkit-slider-thumb {
|
|
-webkit-appearance: none;
|
|
appearance: none;
|
|
height: .5em;
|
|
width: .5em;
|
|
background: #00FF00;
|
|
cursor: pointer;
|
|
}
|
|
#volume::-moz-range-thumb, .volume::-webkit-slider-thumb {
|
|
background: black;
|
|
width: .5em;
|
|
cursor: pointer;
|
|
}
|
|
select {
|
|
background: black;
|
|
border-color: #00FF00;
|
|
color: #00FF00;
|
|
cursor: pointer;
|
|
font-size: inherit;
|
|
}
|
|
select:focus{
|
|
outline:none;
|
|
}
|
|
</style>
|
|
</head>
|
|
<audio id="player" src=""></audio>
|
|
<body>
|
|
<div>
|
|
<pre id="stream-info"></pre>
|
|
<div id="controls">
|
|
<select id="streamSelection">
|
|
<option value="" disabled selected style="display:none;">Select a Station</option>
|
|
</select>
|
|
<button id="playButton">Play</button>
|
|
<div id="volume-container">
|
|
<label for="volume" style="vertical-align: middle">Volume: </label>
|
|
<input id="volume" type="range" min="0" max="100" value="100">
|
|
</div>
|
|
<button id="muteButton">Mute</button>
|
|
</div>
|
|
</div>
|
|
<body>
|
|
<script>
|
|
function loadStreamChoices(data) {
|
|
var selectItem = document.getElementById('streamSelection')
|
|
var streams = data["streams"]
|
|
for(let i in streams) {
|
|
var opt = document.createElement('option')
|
|
if(data.https_streams == true) {
|
|
opt.value = "https://"
|
|
} else {
|
|
opt.value = "http://"
|
|
}
|
|
opt.value = opt.value + data.hostname + ":" + data.port + streams[i]["local_mount"]
|
|
opt.innerHTML = streams[i]["name"]
|
|
selectItem.appendChild(opt)
|
|
}
|
|
}
|
|
|
|
var configXMLHTTP = new XMLHttpRequest();
|
|
configXMLHTTP.onreadystatechange = function() {
|
|
if (this.readyState == 4 && this.status == 200) {
|
|
CONFIG_DATA = JSON.parse(this.responseText);
|
|
renderStreamInfo()
|
|
loadStreamChoices(CONFIG_DATA)
|
|
}
|
|
};
|
|
configXMLHTTP.open("GET", "/config.json", true);
|
|
configXMLHTTP.send();
|
|
</script>
|
|
<script>
|
|
var UPDATE_INTERVAL = window.setInterval(renderStreamInfo, 10000)
|
|
|
|
function buildTitleLine(width, title) {
|
|
width = width - 2 // Subtracting 2 here to account for "|"
|
|
var spacing = Math.floor((width - title.length)/2)
|
|
var line = "|" + " ".repeat(spacing) + title + " ".repeat(spacing)
|
|
if(spacing + title.length + spacing != width) {
|
|
line = line + " |" // Handle title not being even
|
|
} else {
|
|
line = line + "|"
|
|
}
|
|
return line
|
|
}
|
|
|
|
function getSourceInfo(data) {
|
|
var sources = data["icestats"]["source"]
|
|
for(let i in sources) {
|
|
var selectedStream = document.getElementById('streamSelection').value.replace(/^.*[\\\/]/, '')
|
|
var icecastStream = sources[i]["listenurl"].replace(/^.*[\\\/]/, '')
|
|
if(selectedStream == icecastStream) {
|
|
sources[i]["streamurl"] = document.getElementById('streamSelection').value
|
|
return sources[i]
|
|
}
|
|
}
|
|
}
|
|
|
|
function fetchText(statusData, key) {
|
|
var text = ""
|
|
var streamInfo = getSourceInfo(statusData)
|
|
if(streamInfo && streamInfo[key]) {
|
|
text = String(streamInfo[key])
|
|
}
|
|
return text
|
|
}
|
|
|
|
function renderedLength(text) {
|
|
var regEx = /&#\d+;/g
|
|
if (text.match(regEx) !== null) {
|
|
return text.match(/&#\d+;|./g).length
|
|
}
|
|
return text.length
|
|
}
|
|
|
|
function trimLine(line, width) {
|
|
// If line has unicode char, a different trim is needed due to rendering
|
|
// unicode characters like "面" as one "character".
|
|
var unicodeChars = line.match(/&#\d+;/)
|
|
if (unicodeChars !== null) {
|
|
var chars = line.match(/&#\d+;|./g)
|
|
var numUnicodes = line.match(/&#\d+;/g).length
|
|
chars.splice(-1, 1) // Remove "|"
|
|
var text = chars.join("")
|
|
var fudge = 0.69 // TODO: Fix this...non-monospaced fonts may kill me
|
|
var spacing = width - renderedLength(text) - (numUnicodes * fudge)
|
|
if (spacing < 0) {
|
|
spacing = 0
|
|
}
|
|
return text + " ".repeat(spacing) + "|"
|
|
} else {
|
|
if (line.length > width) {
|
|
var end = (width - line.length)
|
|
var start = width - 2 // Subtract 2 for " |"
|
|
return line.substring(end, start) + " |"
|
|
}
|
|
}
|
|
return line
|
|
}
|
|
|
|
function buildLine(width, text, prefix) {
|
|
prefix = "|" + prefix
|
|
|
|
// Subtract 2 for " |"
|
|
var spacing = (width - prefix.length - text.length - 2)
|
|
if(spacing < 0) {
|
|
spacing = 0
|
|
}
|
|
|
|
var line = prefix + text + " ".repeat(spacing) + " |"
|
|
|
|
return trimLine(line, width)
|
|
}
|
|
|
|
function buildDownloadLine(width, url) {
|
|
var line = "| Download: "
|
|
if (url != "") {
|
|
line = line + "<a href='" + url + ".m3u" + "'>M3U</a>"
|
|
line = line + "/"
|
|
line = line + "<a href='" + url + ".xspf" + "'>XSPF</a>"
|
|
line = line + " ".repeat(width - 24) + " |"
|
|
} else {
|
|
line = line + " ".repeat(width) + " |"
|
|
}
|
|
return line
|
|
}
|
|
|
|
function updateStreamInfo() {
|
|
var statusXMLHTTP = new XMLHttpRequest();
|
|
statusXMLHTTP.onreadystatechange = function() {
|
|
if (this.readyState == 4 && this.status == 200) {
|
|
var streamData = JSON.parse(this.responseText);
|
|
var songName = fetchText(streamData, "title")
|
|
var listeners = fetchText(streamData, "listeners")
|
|
var streamURL = fetchText(streamData, "streamurl")
|
|
renderStreamInfo(songName, listeners, streamURL)
|
|
}
|
|
};
|
|
statusXMLHTTP.open("GET", "/status-json.xsl", true);
|
|
statusXMLHTTP.send();
|
|
}
|
|
|
|
function renderStreamInfo(songName="", listeners="", streamURL="") {
|
|
var width = 60
|
|
|
|
// Use an empty title while config.json is being loaded
|
|
if (typeof CONFIG_DATA !== 'undefined') {
|
|
var titleLine = buildTitleLine(width, CONFIG_DATA["title"])
|
|
} else {
|
|
var titleLine = buildTitleLine(width, "")
|
|
}
|
|
|
|
var songNameLine = buildLine(width, songName, " Song Title: ")
|
|
var listenersLine = buildLine(width, listeners, " Listeners: ")
|
|
var downloadLine = buildDownloadLine(width, streamURL)
|
|
|
|
var streamLines = []
|
|
streamLines.push("/" + "-".repeat(width - 2) + "\\")
|
|
streamLines.push(titleLine)
|
|
|
|
if (songName == "" && listeners == "" && streamURL != "") {
|
|
songNameLine = buildLine(width, "Fetching...", " Song Title: ")
|
|
listenersLine = buildLine(width, "Fetching...", " Listeners: ")
|
|
|
|
streamLines.push("|" + " ".repeat(width - 2) + "|")
|
|
streamLines.push(songNameLine)
|
|
streamLines.push(listenersLine)
|
|
}
|
|
|
|
if (songName != "") {
|
|
streamLines.push("|" + " ".repeat(width - 2) + "|")
|
|
streamLines.push(songNameLine)
|
|
}
|
|
|
|
if (songName == "" && listeners != "") {
|
|
streamLines.push("|" + " ".repeat(width - 2) + "|")
|
|
songNameLine = buildLine(width, "Fetching...", " Song Title: ")
|
|
streamLines.push(songNameLine)
|
|
streamLines.push(listenersLine)
|
|
} else if (listeners != "") {
|
|
streamLines.push(listenersLine)
|
|
}
|
|
|
|
if (streamURL != "") {
|
|
streamLines.push(downloadLine)
|
|
streamLines.push("|" + " ".repeat(width - 2) + "|")
|
|
}
|
|
streamLines.push("\\" + "-".repeat(width - 2) + "/")
|
|
|
|
document.getElementById("stream-info").innerHTML = streamLines.join("\n")
|
|
}
|
|
|
|
function setUpdateInterval(interval) {
|
|
window.clearInterval(UPDATE_INTERVAL)
|
|
UPDATE_INTERVAL = window.setInterval(updateStreamInfo, interval)
|
|
updateStreamInfo()
|
|
}
|
|
|
|
window.onload = function(){
|
|
updateStreamInfo()
|
|
}
|
|
|
|
streamSelection.onchange = function() {
|
|
player.src = this.value
|
|
document.title = this.options[this.selectedIndex].text
|
|
player.load()
|
|
player.play()
|
|
}
|
|
|
|
player.onwaiting = function() {
|
|
updateStreamInfo()
|
|
setUpdateInterval(100)
|
|
playButton.innerHTML = "Loading..."
|
|
return;
|
|
}
|
|
player.onplaying = function() {
|
|
updateStreamInfo()
|
|
setUpdateInterval(10000)
|
|
playButton.innerHTML = "Pause"
|
|
return;
|
|
}
|
|
player.onpause = function() {
|
|
updateStreamInfo()
|
|
setUpdateInterval(60000)
|
|
playButton.innerHTML = "Play"
|
|
return;
|
|
}
|
|
|
|
playButton.onclick = function() {
|
|
if(player.paused) {
|
|
player.play();
|
|
} else {
|
|
player.pause();
|
|
}
|
|
}
|
|
|
|
muteButton.onclick = function() {
|
|
if(player.muted) {
|
|
player.muted = false;
|
|
this.innerHTML = "Mute"
|
|
} else {
|
|
player.muted = true;
|
|
this.innerHTML = "Muted"
|
|
}
|
|
}
|
|
|
|
volume.oninput = function() {
|
|
player.volume = this.value/100;
|
|
}
|
|
</script>
|
|
</html>
|
|
|