diff options
author | heqnx <root@heqnx.com> | 2025-07-05 13:47:40 +0300 |
---|---|---|
committer | heqnx <root@heqnx.com> | 2025-07-05 13:47:40 +0300 |
commit | 7ff2aaef6717b97278d97e40143e0dec22924fa5 (patch) | |
tree | 2124d5358bdb58caf9147f2a022e2dc65c8ae458 /templates | |
parent | e95318bcada0feb1dcdd44773e690b2addc6de38 (diff) | |
download | ansible-icecast2-7ff2aaef6717b97278d97e40143e0dec22924fa5.tar.gz ansible-icecast2-7ff2aaef6717b97278d97e40143e0dec22924fa5.zip |
improvements, added metadata to script convertor, added spinnet, added track info to html
Diffstat (limited to 'templates')
-rw-r--r-- | templates/icecast2/mp3-to-ogg.sh.j2 | 12 | ||||
-rw-r--r-- | templates/nginx/nginx.conf.j2 | 19 | ||||
-rw-r--r-- | templates/var/www/html/index.html | 88 | ||||
-rw-r--r-- | templates/var/www/html/style.css | 105 |
4 files changed, 219 insertions, 5 deletions
diff --git a/templates/icecast2/mp3-to-ogg.sh.j2 b/templates/icecast2/mp3-to-ogg.sh.j2 index b37f5af..f475d36 100644 --- a/templates/icecast2/mp3-to-ogg.sh.j2 +++ b/templates/icecast2/mp3-to-ogg.sh.j2 @@ -10,10 +10,9 @@ DIR="{{ radio_music_dir }}" shopt -s nullglob for mp3file in "${DIR}"/*.mp3; do oggfile="${mp3file%.mp3}.ogg" + printf "%s\n" "[inf] converting ${mp3file} to ${oggfile} with ${title}" - printf "%s\n" "[inf] converting ${mp3file} to ${oggfile}" - - if ffmpeg -loglevel error -y -i "${mp3file}" -acodec libvorbis -q:a 5 "${oggfile}"; then + if ffmpeg -loglevel error -y -i "${mp3file}" -acodec libvorbis -q:a 5 -metadata title="${title}" "${oggfile}"; then printf "%s\n" "[inf] conversion successful, removing ${mp3file}" rm -f "${mp3file}" else @@ -21,7 +20,13 @@ for mp3file in "${DIR}"/*.mp3; do fi done +for oggfile in "${DIR}"/*.ogg; do + title="$(basename "${oggfile}" .ogg)" + vorbiscomment -w -t "TITLE=${title}" "${oggfile}" +done + ls "${DIR}"/*.ogg > "${DIR}/playlist.txt" +printf "%s\n" "[inf] playlist generated at ${DIR}/playlist.txt" if id -u icecast2 >/dev/null 2>&1 && getent group icecast >/dev/null 2>&1; then chown -R icecast2:icecast "$DIR" @@ -29,4 +34,3 @@ if id -u icecast2 >/dev/null 2>&1 && getent group icecast >/dev/null 2>&1; then else printf "%s\n" "[err] user or group icecast2:icecast does not exist, skipping chown" fi - diff --git a/templates/nginx/nginx.conf.j2 b/templates/nginx/nginx.conf.j2 index 7f1ac2b..2e257e0 100644 --- a/templates/nginx/nginx.conf.j2 +++ b/templates/nginx/nginx.conf.j2 @@ -77,7 +77,7 @@ http { } location /stream { - proxy_pass http://localhost:8000/stream; + proxy_pass http://127.0.0.1:8000/stream; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; @@ -96,6 +96,23 @@ http { add_header Access-Control-Expose-Headers "Content-Length,Content-Range" always; } + location /nowplaying { + proxy_pass http://127.0.0.1:8000/status-json.xsl; + + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto $scheme; + + proxy_http_version 1.1; + proxy_buffering off; + proxy_request_buffering off; + + add_header Access-Control-Allow-Origin "*" always; + add_header Access-Control-Allow-Methods "GET, OPTIONS" always; + add_header Access-Control-Allow-Headers "Content-Type" always; + } + location /admin { deny all; return 403; diff --git a/templates/var/www/html/index.html b/templates/var/www/html/index.html new file mode 100644 index 0000000..14ed148 --- /dev/null +++ b/templates/var/www/html/index.html @@ -0,0 +1,88 @@ +<!DOCTYPE html> +<html lang="en"> +<head> + <meta charset="UTF-8" /> + <meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no" /> + <link rel="icon" href="data:," /> + <link rel="stylesheet" href="style.css" /> + <title>{{ radio_name }}</title> +</head> +<body> + <div class="index-container"> + <div class="player-wrapper"> + <a id="radioButton" class="btn" href="#">Play</a> + <div id="info"></div> + <audio id="radioPlayer" hidden> + <source src="/stream" type="audio/ogg" /> + Your browser does not support the audio element. + </audio> + </div> + </div> + + <script> + const player = document.getElementById('radioPlayer'); + const button = document.getElementById('radioButton'); + const infoEl = document.getElementById('info'); + + button.addEventListener('click', () => { + if (player.paused) { + player.play(); + button.textContent = 'Stop'; + } else { + player.pause(); + player.currentTime = 0; + button.textContent = 'Play'; + } + }); + + let spinnerInterval; + + function startSpinner() { + const spinnerChars = ['|', '/', '-', '\\']; + let i = 0; + spinnerInterval = setInterval(() => { + infoEl.textContent = `${spinnerChars[i++ % spinnerChars.length]}`; + }, 100); + } + + function stopSpinner() { + clearInterval(spinnerInterval); + } + + let spinnerHasRun = false; + + async function fetchCurrentTrack() { + if (!spinnerHasRun) { + startSpinner(); + await new Promise(resolve => setTimeout(resolve, 3000)); + } + + try { + const response = await fetch('/info'); + if (!response.ok) throw new Error('Network response was not ok'); + + const data = await response.json(); + const source = data.icestats.source; + + const title = source.title || 'Unknown'; + const bitrate = source['ice-bitrate'] || 0; + const listeners = source.listeners || 0; + const listenerLabel = listeners === 1 ? 'listener' : 'listeners'; + + stopSpinner(); + infoEl.textContent = `${title} | ${bitrate} kbps | ${listeners} ${listenerLabel}`; + + spinnerHasRun = true; + + } catch (error) { + stopSpinner(); + infoEl.textContent = 'Error loading track info'; + console.error(error); + } + } + + fetchCurrentTrack(); + setInterval(fetchCurrentTrack, 10000); + </script> +</body> +</html> diff --git a/templates/var/www/html/style.css b/templates/var/www/html/style.css new file mode 100644 index 0000000..542b59c --- /dev/null +++ b/templates/var/www/html/style.css @@ -0,0 +1,105 @@ +:root { + --color-black: #000000; + --color-dark-cyan: #008080; + --color-light-gray: #C0C0C0; + --color-dark-gray: #808080; +} + +@font-face { + font-family: 'MorePerfectDOSVGA'; + src: url('MorePerfectDOSVGA.ttf') format('truetype'); +} + +::-webkit-scrollbar { + display: none; +} + +* { + -ms-overflow-style: none; + scrollbar-width: none; +} + +body { + background-color: var(--color-black); + color: var(--color-light-gray); + font-family: 'MorePerfectDOSVGA', monospace; + font-size: 14px; + line-height: 1.6; + margin: 0 auto; + padding: 0; + overflow: hidden; + display: flex; + align-items: center; + justify-content: center; + height: 100vh; + text-align: center; +} + +.index-container { + display: flex; + justify-content: center; + align-items: center; + height: 100vh; + width: 100%; + padding: 0 10px; + box-sizing: border-box; +} + +.player-wrapper { + display: flex; + flex-direction: column; + align-items: center; + gap: 8px; + max-width: 100%; + width: 320px; +} + +.btn { + color: var(--color-black); + background-color: var(--color-light-gray); + padding: 8px 16px; + text-decoration: none; + font-size: 16px; + font-weight: bold; + position: relative; + display: inline-block; + cursor: pointer; + user-select: none; +} + +.btn::before { + content: ''; + background-color: var(--color-dark-gray); + position: absolute; + top: 4px; + left: 4px; + width: 100%; + height: 100%; + z-index: -1; +} + +.btn:hover::before { + background-color: var(--color-dark-cyan); +} + +#info { + font-size: 14px; + color: var(--color-light-gray); + min-height: 18px; + white-space: nowrap; + overflow: hidden; + text-overflow: ellipsis; +} + +@media (max-width: 400px) { + .btn { + font-size: 14px; + padding: 6px 12px; + } + #info { + font-size: 12px; + } + .player-wrapper { + width: 90vw; + } +} |