diff --git a/files/config.example.js b/files/config.example.js
deleted file mode 100644
index 1a61c58..0000000
--- a/files/config.example.js
+++ /dev/null
@@ -1,3 +0,0 @@
-exports.host = "0.0.0.0";
-exports.port = 32202;
-exports.share_path = "/srv/ftp";
\ No newline at end of file
diff --git a/files/index.js b/files/index.js
deleted file mode 100644
index 1c65332..0000000
--- a/files/index.js
+++ /dev/null
@@ -1,117 +0,0 @@
-const fs = require("fs");
-const path = require("path");
-const { ENOENT, ECONNRESET } = require("constants");
-
-const Koa = require("koa");
-const koaPug = require("koa-pug");
-const koaRouter = require("koa-router");
-
-const config = require("./config");
-const util = require("../shared/util");
-
-
-
-const SIZE_UNITS = [ "B", "KiB", "MiB", "GiB" ];
-
-function addFileSizeUnit(size) {
- let i = 0;
- for (; size > 1024; size /= 1024) ++i;
- return [i > 0 ? size.toFixed(2) : size, SIZE_UNITS[i]];
-}
-
-
-function buildCurrentPathLink(path) {
- let currentPath = `root`;
-
- if (path.endsWith("/"))
- path = path.slice(1, path.length-1);
-
- const path_parts = path.split("/");
-
- for (let i = 0; i < path_parts.length; ++i) {
- let lnk = "";
-
- for (let j = 0; j < i+1; ++j)
- lnk += `/${path_parts[j]}`;
-
- currentPath += `/${path_parts[i]}`;
- }
-
- return currentPath;
-}
-
-function sortFuncByNameField(a, b) {
- return (a.name > b.name) ? 1 : (a.name < b.name) ? -1 : 0;
-}
-
-async function getDirectoryList(dir_path, orig_url) {
- let directories = [];
- let files = [];
- let total_files_size = 0;
- let directory = await fs.promises.opendir(dir_path);
-
- for await (const dirent of directory) {
- const stat = await fs.promises.stat(path.join(dir_path, dirent.name));
- const [s, u] = addFileSizeUnit(stat.size);
-
- if (stat.isDirectory())
- directories.push({
- name: dirent.name,
- link: `${encodeURIComponent(dirent.name)}/`,
- datetime: stat.mtime,
- size: "DIR" });
- else {
- total_files_size += stat.size;
- files.push({
- name: dirent.name,
- link: `/files${orig_url}${encodeURIComponent(dirent.name)}`,
- datetime: stat.mtime,
- size: `${s} ${u}` });
- }
- }
-
- directories.sort(sortFuncByNameField);
- files.sort(sortFuncByNameField);
-
- return [directories.concat(files), directories.length, files.length,
- addFileSizeUnit(total_files_size).join(' ')];
-}
-
-function setRoutes() {
- return koaRouter().get('/(.*)?', async ctx => {
- const file_path = path.join(config.share_path, decodeURI(ctx.originalUrl));
-
- let stat = await fs.promises.stat(file_path);
-
- if (stat.isDirectory()) {
- const [items, total_directories, total_files, total_files_size]
- = await getDirectoryList(file_path, decodeURI(ctx.originalUrl));
-
- await ctx.render('index', {
- clientTZ: util.getClientTimezone(ctx),
- main_site: util.getServiceByHost(ctx.header.host),
- current_path: buildCurrentPathLink(decodeURI(ctx.originalUrl)),
- total_files: total_files,
- total_files_size: total_files_size,
- total_directories: total_directories,
- items: items });
- }
- });
-}
-
-
-const app = new Koa();
-const pug = new koaPug({
- viewPath: path.join(__dirname, "views"),
- locals: {
- moment: (date, tz) => util.datetime(date, util.date_formats.file_date, tz), },
- app: app
-});
-
-app.proxy = true;
-app
- .use(setRoutes().routes())
- // .on("error", err => { if (!err.code == ECONNRESET || !err.code == ENOENT) console.log(err.code); })
- .listen(config.port, config.host);
-
-console.log("Arav's dwelling / Files is up.");
\ No newline at end of file
diff --git a/files/static/assets/css/main.css b/files/static/assets/css/main.css
deleted file mode 100644
index 555cf67..0000000
--- a/files/static/assets/css/main.css
+++ /dev/null
@@ -1,170 +0,0 @@
-@font-face {
- font-family: 'Roboto Condensed';
- font-style: normal;
- font-weight: 400;
- src: local('RobotoCondensed'), local('RobotoCondensed-Regular'),
- url(/shared/fonts/RobotoCondensed-Regular.ttf); }
-
-:root {
- --background-color: #0a0a0a;
- --primary-color: #cd2682;
- --secondary-color: #9f2b68;
- --text-color: #f5f5f5;
- --text-indent: 1.6rem;
- --overlay-background-color: #f5f5f574;
- scrollbar-color: var(--primary-color) var(--background-color); }
-
-@media (prefers-color-scheme: light) {
- :root {
- --background-color: #f5f5f5;
- --primary-color: #9f2b68;
- --secondary-color: #cd2682;
- --text-color: #0a0a0a;
- --overlay-background-color: #0a0a0a74; } }
-
-* { margin: 0; }
-
-::selection {
- background-color: var(--secondary-color);
- color: var(--background-color); }
-
-a {
- color: var(--primary-color);
- text-decoration: none; }
-
-a:hover {
- color: var(--secondary-color);
- text-decoration: underline;
- text-decoration-style: dotted;
- transition: .5s; }
-
-p {
- text-align: justify;
- line-height: var(--text-indent);
- text-indent: var(--text-indent); }
-
-p:not(:last-child) { margin-bottom: .1rem; }
-
-h1,
-h2 {
- font-size: 1.8rem;
- font-variant: small-caps;
- text-align: center;
- margin-bottom: 1rem; }
-
-h2 {
- font-size: 1.4rem;
- margin: 1rem 0; }
-
-html { margin-left: calc(100vw - 100%); }
-
-body {
- background-color: var(--background-color);
- color: var(--text-color);
- font-family: 'Roboto Condensed', Roboto, sans-serif;
- font-size: 1.1rem;
- margin: 0 auto;
- max-width: 960px;
- width: 98%; }
-
-header {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between; }
-
-#logo {
- display: block;
- width: 360px; }
-
-#logo text { fill: var(--text-color); }
-
-#logo .logo {
- font-size: 2rem;
- font-variant-caps: small-caps;
- font-weight: bold; }
-
-@media screen and (-webkit-min-device-pixel-ratio:0) {
- #logo .logo { font-size: 2.082rem; } }
-
-@-moz-document url-prefix() {
- #logo .logo { font-size: 2rem; } }
-
-#logo .under { font-size: .88rem; }
-
-nav { margin-top: .5rem; }
-
-nav a { font-variant: small-caps; }
-
-nav h1 {
- color: var(--secondary-color);
- margin: 0; }
-
-section { margin-top: 1rem; }
-
-#overlay {
- align-items: center;
- background-color: var(--overlay-background-color);
- display: flex;
- flex-direction: column;
- height: 100%;
- left: 0;
- max-height: 100%;
- position: fixed;
- top: 0;
- visibility: hidden;
- width: 100%; }
-
-#overlay video,
-#overlay audio,
-#overlay img {
- margin: auto;
- max-height: 100%;
- max-width: 86%;
-}
-
-#overlay span {
- color: var(--background-color);
- text-shadow: 0 0 .3rem var(--secondary-color);
-}
-
-table { overflow-y: scroll; width: 100%; }
-
-tr { vertical-align: top; }
-
-tr:hover { background-color: var(--primary-color); color: white; }
-
-tr:hover a { color: white; }
-
-th { text-align: left; }
-
-th:nth-child(2),
-th:last-child { width: 1%; white-space: nowrap; }
-
-th,
-td { line-break: strict; }
-
-th:nth-child(2),
-td:nth-child(2) { padding: 0 1rem; }
-
-td a { display: block; width: 100%; }
-
-td a:hover { transition: none; }
-
-td:nth-child(2),
-td:last-child { white-space: nowrap; }
-
-footer {
- font-size: .8rem;
- text-align: center;
- padding: 1rem 0; }
-
-@media screen and (max-width: 640px) {
- header { display: block; }
-
- #logo {
- margin: 0 auto;
- width: 100%; }
-
- nav {
- width: 100%;
- text-align: center; } }
\ No newline at end of file
diff --git a/files/static/assets/js/main.js b/files/static/assets/js/main.js
deleted file mode 100644
index 2779545..0000000
--- a/files/static/assets/js/main.js
+++ /dev/null
@@ -1,56 +0,0 @@
-const video_formats = ["webm", "mp4"];
-const audio_formats = ["mp3", "flac", "opus", "ogg", "m4a"];
-const image_formats = ["jpg", "jpeg", "gif", "png", "bmp", "webp"];
-
-const overlay = document.getElementById("overlay");
-let g_scale = 1;
-let g_volume = 1.0;
-
-function mousescroll(e) {
- e.preventDefault();
- g_scale = Math.min(Math.max(0.25, g_scale + (e.deltaY * -0.001)), 4);
- e.target.style.transform = `scale(${g_scale})`;
-}
-
-function onvolumechange(e) {
- g_volume = e.target.volume;
-}
-
-const ext_filter = (ext, pathname) => pathname.toLowerCase().endsWith(ext);
-
-function to_overlay(eltyp, pathname) {
- const el = document.createElement(eltyp);
- const el_label = document.createElement("span");
- el_label.textContent = decodeURI(pathname.substr(pathname.lastIndexOf("/") + 1));
- if (eltyp !== "audio") el.addEventListener('wheel', mousescroll);
- if (eltyp !== "img") {
- el.autoplay = el.controls = true;
- el.addEventListener("volumechange", onvolumechange);
- el.volume = g_volume;
- }
- el.src = pathname;
- overlay.appendChild(el);
- overlay.appendChild(el_label);
- overlay.style.visibility = "visible";
-}
-
-document.getElementById("overlay").addEventListener("click", e => {
- e.target.firstChild.remove();
- e.target.firstChild.remove();
- e.target.style.visibility = "hidden";
- g_scale = 1;
-});
-
-const file_links = Array.from(document.getElementsByTagName('tr')).slice(2).filter(e => e.lastChild.innerHTML != "DIR").map(l => l.firstChild.firstChild);
-
-file_links.forEach(f => f.addEventListener('click', e => {
- const pathname = e.target.pathname;
- if (video_formats.some(ext => ext_filter(ext, pathname)))
- to_overlay("video", pathname);
- else if (audio_formats.some(ext => ext_filter(ext, pathname)))
- to_overlay("audio", pathname);
- else if (image_formats.some(ext => ext_filter(ext, pathname)))
- to_overlay("img", pathname);
- if (overlay.firstChild != null)
- e.preventDefault();
-}));
diff --git a/files/static/robots.txt b/files/static/robots.txt
deleted file mode 100644
index 8811370..0000000
--- a/files/static/robots.txt
+++ /dev/null
@@ -1,2 +0,0 @@
-User-agent: *
-Disallow: /assets/
\ No newline at end of file
diff --git a/files/views/index.pug b/files/views/index.pug
deleted file mode 100644
index 36af7ba..0000000
--- a/files/views/index.pug
+++ /dev/null
@@ -1,44 +0,0 @@
-doctype html
-html(lang='en')
- head
- title Arav's dwelling / Files
- meta(charset='utf-8')
- meta(http-equiv='X-UA-Compatible' content='IE=edge')
- meta(name='viewport' content='width=device-width, initial-scale=1.0')
- meta(name='theme-color' content='#cd2682')
- meta(name='description' content='My file share.')
- link(rel='icon' type='image/svg+xml' href='/shared/img/favicon.svg' sizes='any')
- link(href='/assets/css/main.css' rel='stylesheet')
- script(src='/assets/js/main.js' defer)
- body
- header
- svg#logo(viewBox='0 -25 216 40')
- text.logo Arav's dwelling
- text.under(y='11') Welcome to my sacred place, wanderer
- nav
- a(href=main_site) Back to main website
- h1 Files
- section#files
- span#current-path!= current_path
- p Files: #{total_files} (#{total_files_size}); Directories: #{total_directories}.
- table
- thead
- tr
- th Name
- th Date
- th Size
- tbody
- tr
- td #[a(href="../") ../]
- each item in items
- tr
- td #[a(href=item.link)= item.name]
- td!= moment(item.datetime, clientTZ)
- td= item.size
- section#privacy
- h2 Privacy statements
- p I collect access logs that include access date and time, IP-address, User-Agent, referer URL that tells me where have you came from, request that you sent to me. In addition there are GeoIP information added based on your IP-address that includes country, region, and city for my convenience.
- p This site makes use of JavaScript purely for convenient functionality, like being able to watch video, listen to music, and look images in an overlay without the need to open a file in a new tab or return back.
- footer
- | 2017—2022 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>
- div#overlay
\ No newline at end of file
diff --git a/radio/config.example.js b/radio/config.example.js
deleted file mode 100644
index 692db11..0000000
--- a/radio/config.example.js
+++ /dev/null
@@ -1,2 +0,0 @@
-exports.port = 32251;
-exports.host = "127.0.0.1";
diff --git a/radio/index.js b/radio/index.js
deleted file mode 100644
index 4d00d8e..0000000
--- a/radio/index.js
+++ /dev/null
@@ -1,79 +0,0 @@
-const fs = require("fs");
-const path = require("path");
-const uti = require("util");
-const exec = uti.promisify(require("child_process").exec);
-
-const Koa = require("koa");
-const koaPug = require("koa-pug");
-const koaRouter = require("koa-router");
-const fetch = require("node-fetch");
-const moment = require("moment-timezone");
-
-const config = require("./config");
-const util = require("../shared/util");
-
-async function getRadioStatus() {
- try {
- let status = await fetch('http://radio.arav.home.arpa/status-json.xsl').then(r => r.json());
- return {
- server_start_iso8601: status.icestats.source.stream_start_iso8601,
- server_start_date: util.datetime(status.icestats.source.stream_start_iso8601, util.date_formats.post_date),
- song: `${status.icestats.source.artist} - ${status.icestats.source.title}`,
- listener_peak: status.icestats.source.listener_peak,
- listeners: status.icestats.source.listeners
- }
- } catch {
- return {
- server_start_iso8601: "n/a",
- server_start_date: "n/a",
- song: "n/a",
- listener_peak: "n/a",
- listeners: "n/a"
- }
- }
-}
-
-async function getLastPlayedSongs(count, tz) {
- try {
- const { stdout, _ } = await exec(`tail -n${count} /var/log/icecast/playlist.log | head -n-1 | cut -d"|" -f1,4`);
- let songs = stdout.trim().split("\n");
- for (let i = 0; i < songs.length; ++i) {
- let [t, s] = songs[i].split('|');
- t = moment(t, "DD/MMM/YYYY:HH:mm:ss ZZ").tz(tz).format("HH:mm");
- let song = s.split(' - ');
- songs[i] = { "start_time_local": t, "artist": song[0], "title": song[1] };
- }
- return songs;
- } catch {
- return [];
- }
-}
-
-function setRoutes() {
- return koaRouter().get('/', async ctx => {
- await ctx.render('index', {
- main_site: util.getServiceByHost(ctx.header.host),
- radio_status: await getRadioStatus(),
- last_songs: await getLastPlayedSongs(11, util.getClientTimezone(ctx))
- });
- })
- .get('/stats', async ctx => {
- ctx.body = await getRadioStatus();
- })
- .get('/lastsong', async ctx => {
- ctx.body = (await getLastPlayedSongs(2, util.getClientTimezone(ctx)))[0];
- });
-}
-
-const app = new Koa();
-const pug = new koaPug({
- viewPath: path.join(__dirname, "views"),
- locals: {
- moment: util.datetime },
- app: app
-});
-
-app.proxy = true;
-app
- .use(setRoutes().routes())
- .listen(config.port, config.host);
diff --git a/radio/static/assets/css/main.css b/radio/static/assets/css/main.css
deleted file mode 100644
index 97dc940..0000000
--- a/radio/static/assets/css/main.css
+++ /dev/null
@@ -1,154 +0,0 @@
-@font-face {
- font-family: 'Roboto Condensed';
- font-style: normal;
- font-weight: 400;
- src: local('RobotoCondensed'), local('RobotoCondensed-Regular'),
- url(/shared/fonts/RobotoCondensed-Regular.ttf); }
-
-:root {
- --background-color: #0a0a0a;
- --primary-color: #cd2682;
- --secondary-color: #9f2b68;
- --text-color: #f5f5f5;
- --text-indent: 1.6rem;
- scrollbar-color: var(--primary-color) var(--background-color); }
-
-@media (prefers-color-scheme: light) {
- :root {
- --background-color: #f5f5f5;
- --primary-color: #9f2b68;
- --secondary-color: #cd2682;
- --text-color: #0a0a0a; } }
-
-* { margin: 0; }
-
-::selection {
- background-color: var(--secondary-color);
- color: var(--background-color); }
-
-a,
-button {
- color: var(--primary-color);
- text-decoration: none; }
-
-a:hover,
-button:hover {
- color: var(--secondary-color);
- cursor: pointer;
- text-decoration: underline dotted;
- transition: .5s; }
-
-button {
- background: none;
- border: none;
- font: inherit;
- padding: 0; }
-
-p {
- text-align: justify;
- line-height: var(--text-indent);
- text-indent: var(--text-indent); }
-
-p:not(:last-child) { margin-bottom: .1rem; }
-
-h1,
-h2 {
- font-size: 1.8rem;
- font-variant: small-caps;
- text-align: center;
- margin-bottom: 1rem; }
-
-h2 {
- font-size: 1.4rem;
- margin: 1rem 0; }
-
-small { font-size: .8rem; }
-
-small.player-links a { margin: 0 .2rem; }
-
-audio {
- background-color: var(--primary-color);
- box-shadow: 5px 5px var(--primary-color);
- width: 100%; }
-
-@media screen and (-webkit-min-device-pixel-ratio:0) {
- audio::-webkit-media-controls-panel {
- background-color: var(--secondary-color); }
-
- audio { border-radius: 1.6rem; } }
-
-@-moz-document url-prefix() {
- audio { border-radius: 0; } }
-
-html { margin-left: calc(100vw - 100%); }
-
-body {
- background-color: var(--background-color);
- color: var(--text-color);
- font-family: 'Roboto Condensed', Roboto, sans-serif;
- font-size: 1.1rem;
- margin: 0 auto;
- max-width: 960px;
- width: 98%; }
-
-header {
- display: flex;
- flex-wrap: wrap;
- justify-content: space-between; }
-
-#logo {
- display: block;
- width: 360px; }
-
-#logo text { fill: var(--text-color); }
-
-#logo .logo {
- font-size: 2rem;
- font-variant-caps: small-caps;
- font-weight: bold; }
-
-@media screen and (-webkit-min-device-pixel-ratio:0) {
- #logo .logo { font-size: 2.082rem; } }
-
-@-moz-document url-prefix() {
- #logo .logo { font-size: 2rem; } }
-
-#logo .under { font-size: .88rem; }
-
-nav { margin-top: .5rem; }
-
-nav a { font-variant: small-caps; }
-
-nav h1 {
- color: var(--secondary-color);
- margin: 0; }
-
-section { margin-top: 1rem; }
-
-#last-played {
- margin: 0 auto;
- min-width: 80%;
- width: 80%; }
-
-#last-played tbody tr {
- display: grid;
- gap: .5rem;
- grid-template-columns: 3rem 1fr 1fr; }
-
-#last-played tbody tr td:nth-child(2) { text-align: right; }
-
-footer {
- font-size: .8rem;
- text-align: center;
- padding: 1rem 0; }
-
-@media screen and (max-width: 640px) {
- header { display: block; }
-
- #logo {
- margin: 0 auto;
- width: 100%; }
-
- nav {
- width: 100%;
- text-align: center; } }
\ No newline at end of file
diff --git a/radio/static/assets/files/radio.arav.top.m3u b/radio/static/assets/files/radio.arav.top.m3u
deleted file mode 100644
index 8730df1..0000000
--- a/radio/static/assets/files/radio.arav.top.m3u
+++ /dev/null
@@ -1,9 +0,0 @@
-#EXTM3U
-#EXTINF:-1,Arav's dwelling / Radio
-http://radio.arav.top:8000/stream.ogg
-#EXTINF:-1,Arav's dwelling / Radio (HTTPS)
-https://radio.arav.top/live/stream.ogg
-#EXTINF:-1,Arav's dwelling / Radio on Tor
-http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg
-#EXTINF:-1,Arav's dwelling / Radio on I2P
-http://radio.arav.i2p/live/stream.ogg
\ No newline at end of file
diff --git a/radio/static/assets/js/main.js b/radio/static/assets/js/main.js
deleted file mode 100644
index f9b23f0..0000000
--- a/radio/static/assets/js/main.js
+++ /dev/null
@@ -1,48 +0,0 @@
-function $(id) { return document.getElementById(id); }
-
-function updateRadioStatus() {
- fetch("/stats")
- .then(r => r.json())
- .then(r => {
- $("radio-status").innerHTML =
- `On-air since `;
- $("radio-song").textContent = r.song;
- $("radio-listeners").textContent = r.listeners;
- $("radio-listener-peak").textContent = r.listener_peak;
- }).catch(() => {
- $("radio-status").textContent = "Radio is offline.";
- $("radio-song").textContent =
- $("radio-listeners").textContent =
- $("radio-listener-peak").textContent = "n/a";
- });
-}
-
-function updateLastPlayedSong() {
- fetch('/lastsong')
- .then(r => r.json())
- .then(last_played => {
- let cur_artist = $('last-played').firstChild.lastChild.children[1].innerText;
- let cur_title = $('last-played').firstChild.lastChild.lastChild.innerText;
-
- if (last_played.artist == cur_artist && last_played.title == cur_title)
- return;
-
- $('last-played').firstChild.firstChild.remove();
-
- let row = $('last-played').insertRow();
- let start_time = row.insertCell();
- start_time.appendChild(document.createTextNode(last_played.start_time_local));
- let artist_cell = row.insertCell();
- artist_cell.appendChild(document.createTextNode(last_played.artist));
- let title_cell = row.insertCell();
- title_cell.appendChild(document.createTextNode(last_played.title));
- });
-}
-
-document.getElementById("btn-update").addEventListener("click", () => {
- updateLastPlayedSong();
- updateRadioStatus();
-})
-
-setInterval(updateRadioStatus, 45000);
-setInterval(updateLastPlayedSong, 45000);
\ No newline at end of file
diff --git a/radio/static/robots.txt b/radio/static/robots.txt
deleted file mode 100644
index 912a23f..0000000
--- a/radio/static/robots.txt
+++ /dev/null
@@ -1,3 +0,0 @@
-User-agent: *
-Disallow: /assets/
-Disallow: /live/
\ No newline at end of file
diff --git a/radio/views/index.pug b/radio/views/index.pug
deleted file mode 100644
index 9de5bf2..0000000
--- a/radio/views/index.pug
+++ /dev/null
@@ -1,60 +0,0 @@
-mixin radioStatus(date, iso)
- if (date != "n/a")
- p #[span#radio-status On-air since #[time(datetime=iso)= date]]
- else
- p #[span#radio-status Radio is offline.]
-
-doctype html
-html(lang='en')
- head
- title Arav's dwelling / Radio
- meta(charset='utf-8')
- meta(http-equiv='X-UA-Compatible' content='IE=edge')
- meta(name='viewport' content='width=device-width, initial-scale=1.0')
- meta(name='theme-color' content='#cd2682')
- meta(name='description' content='Internet-radio broadcasting from under my desk.')
- link(rel='icon' href='/shared/img/favicon.svg' sizes='any' type='image/svg+xml')
- link(href='/assets/css/main.css' rel='stylesheet')
- script(src='/assets/js/main.js' defer)
- body
- header
- svg#logo(viewBox='0 -25 216 40')
- text.logo Arav's dwelling
- text.under(y='11') Welcome to my sacred place, wanderer
- nav
- a(href=main_site) Back to main website
- h1 Radio
- section
- small.player-links
- a(href='/filelist') filelist
- a(href='/playlist') playlist (.m3u)
- a(href='/live/stream.ogg') direct link
- a(href='http://radio.arav.top:8000/stream.ogg') direct link (http)
- a(href='http://wsmkgnmhmzqm7kyzv7jnzzafvgm7xlmlfvzhgorpapd5or2arnhuktqd.onion/live/stream.ogg') direct link (Tor)
- a(href='http://radio.arav.i2p/live/stream.ogg') direct link (I2P)
- a(href='https://dir.xiph.org/search?q=arav\'s+dwelling') Xiph
- | OGG 128 Kb/s
- audio(preload='none' controls)
- source(src='/live/stream.ogg' type='audio/ogg')
- | Your browser doesn't support an audio element, it's sad... But you always can take the #[a(href='/playlist') playlist]!
- +radioStatus(radio_status.server_start_date, radio_status.server_start_iso8601)
- p Now playing: #[span#radio-song= radio_status.song]
- p Current/peak listeners: #[span#radio-listeners= radio_status.listeners] / #[span#radio-listener-peak= radio_status.listener_peak]
- p
- small Notice: information updates every 45 seconds. But you can #[button(id='btn-update') update] it forcibly.
- if (last_songs)
- section
- h2 Last 10 songs
- table#last-played
- each song in last_songs
- tr
- td= song.start_time_local
- td= song.artist
- td= song.title
- section
- p The largest number of simultaneous listeners was #[b 7] at #[time(datetime='2022-02-19') 19 February 2022], and the song was "Röyksopp - 49 Percent".
- section
- h2 Privacy statements
- p Logs are collected and include access date and time, IP-address, User-Agent, referer URL, request. This website makes use of JavaScript to update a radio status and last 10 songs list.
- footer
- | 2017—2022 Arav <#[a(href='mailto:me@arav.top') me@arav.top]>