Compare commits

...

13 Commits

Author SHA1 Message Date
951b00385a Remove debugging "foo" target from Makefile 2025-07-19 22:00:51 -05:00
5c65206c34 correct Mailfile logic for custom/default config template file 2025-07-19 21:52:17 -05:00
8f8bb57fd1 add Makefile to guide configuration and perform installation
This change replaces icecast.xml with icecast.xml.template.  A
complete icecast.xml is no-longer provided.

Instead, to create icecast.xml run:

  make

You can generate icecast.xml and then install it:

  make install

This doesn't currently do any aspectss of making the system ready to
run icecast (e.g. performing `sudo apt install icecast2`, or similar).
2025-07-19 18:59:40 -05:00
7b3f1768d2 Mark duration fix and multi-caster config tasks complete 2025-07-18 23:09:42 +02:00
528fd7f622 read from playlist.log; avoid making the file empty 2025-05-16 22:57:44 -05:00
130693884b put stuff in the readme 2025-05-06 18:51:41 -05:00
437d3129e5 fix self-inflicted 1h time-out issue; prep config gen
The 1h timeout was caused by an express setting in the icecast mount.
Other changes will make search and replace easier, thus scripting to
create the icecast configuration from a script or settings file.
2025-05-06 18:47:33 -05:00
afbd9a1820 add TODO to readme 2025-05-06 04:20:15 +02:00
401ca965fc correct garbeled text in placeholder readme 2025-05-06 04:15:11 +02:00
0ad0aa13fb add nginux icecast configuration 2025-05-05 21:13:00 -05:00
df708844f5 add the HTML player source 2025-05-04 21:46:02 -05:00
d2d60f43cb make track title server side scripting into a service 2025-05-04 21:30:49 -05:00
79af7bfae7 more scripting to get track title title as a static file 2025-05-04 21:28:39 -05:00
9 changed files with 499 additions and 96 deletions

80
Makefile Normal file
View File

@ -0,0 +1,80 @@
# Makefile for shred.ing
# make (default, or with: make config)
# prompt for settings unless .env file exists
# create ~/icecast.xml
# make install
# backup the live config to ~/icecast.xml
# install ~/icecast.xml to /etc/icecast2
# not currently used for anything
## make sure to change these
#source_pass := password_to_share_with_casters
#relay_pass := password_for_icecast2_relays
#admin_user := admin_web_and_api_username
#admin_pass := password_for_admin_access
## probably want to change this
#hostname := shred.ing
# the "prompt" port can be left blank so
# this can be left at default
icecast2_port := 8000
##
## More or less internal stuffs
##
ICECAST_CONFIG_TEMPLATE ?= icecast.xml.template
## generate configuration files for icecast2
# these targets aren't associated with files
.PHONEY: config backup-live-config install foo
## main "entry point" makefile targets
config: icecast.xml .env
backup-live-config:
sudo test -r /etc/icecast2/icecast.xml && sudo cp /etc/icecast2/icecast.xml icecast.xml~~
install: config backup-live-config
sudo cp icecast.xml /etc/icecast2/icecast.xml.new
# this will prompt to build a .env file when none exists
.env:
@echo "You will receive a series of prompts to create a .env file."
@echo
@printf "Enter your icecast hostname (or IP): "
@printf "hostname=%s\n" `read i; echo $$i` >.env
@printf "Enter the port for your icecast server ($(icecast2_port)): "
@printf "icecast2_port=%s\n" "$$( i=`read i; echo $$i`; printf "%s" $$( if test -z "$$i" ; then echo "$(icecast2_port)"; else echo "$$i" ; fi ) )" >>.env
@echo "Icecast requires, potentially, several passwords."
@echo "Passwords entered now are echoed to the screen."
@echo "You can pick bogus values and then edit the .env file."
@printf "Enter a source password: "
@printf "source_pass=%s\n" `read i; echo $$i` >>.env
@printf "Enter a relay password: "
@printf "relay_pass=%s\n" `read i; echo $$i` >>.env
@printf "Enter an admin password: "
@printf "admin_pass=%s\n" `read i; echo $$i` >>.env
@echo "Finally, select a username for the admin account:"
@printf "Enter the icecast admin username: "
@printf "admin_user=%s\n" `read i; echo $$i` >>.env
# this ensures the template icecast config exists
$(ICECAST_CONFIG_TEMPLATE):
@printf "The icecast2 configuration file template "'"'"%s"'"'"\n" "$(ICECAST_CONFIG_TEMPLATE)"
@printf "is missing or cannot be read.\n"
@echo "This can be caused by an incomplete or corrupted checkout or"
@echo "an invalid value for the ICECAST_CONFIG_TEMPLATE env variable."
false
### given we have a template and a .env, create the icecast config
icecast.xml: $(ICECAST_CONFIG_TEMPLATE) .env
perl -e 'BEGIN{open my$$FH,q(<),q(.env) or die $$!; for(<$$FH>){ chomp; my($$k,$$v) = split q(=); next unless $$k; $$k =~ s/^\s+|\s+$$//g; next unless $$k; $$v =~ s/^\s+|\s+//g; $$h{lc $$k} = $$v; }}' -pe 's/\@\@([^@]+)\@\@/$$h{lc $$1}/eig;' >icecast.xml <$(ICECAST_CONFIG_TEMPLATE)
# this seems to work, too!
# cat icecast.xml.template | perl -pe 's/\@\@source_pass\@\@/$(source_pass)/ig; s/\@\@relay_pass\@\@/$(relay_pass)/ig; s/\@\@admin_user\@\@/$(admin_user)/ig; s/\@\@admin_pass\@\@/$(admin_pass)/ig; s/\@\@hostname\@\@/$(hostname)/ig; s/\@\@icecast2_port\@\@/$(icecast2_port)/ig;' > icecast.xml
# probably, so does this?
# cat icecast.xml.template | perl -e 'BEGIN{open my$FH,q(<),q(.env) or die $!; for(<$FH>){ chomp; my($k,$v) = split q(=); next unless $k; $k =~ s/^\s+|\s+$//g; next unless $k; $v =~ s/^\s+|\s+//g; $h{lc $k} = $v; }}' -pe 's/\@\@([^@]+)\@\@/$h{lc $1}/eig;' -e 'END { use Data::Dumper; warn Dumper( \%h ) } ' > foo.xml

View File

@ -1,3 +1,51 @@
# shred.ing-server # shred.ing-server
Stuff to install on a (nominally) Ubuntu 24 host to create (e.g.) shred.ing to create an internet hosted radio station. Stuff to install on a (nominally) Ubuntu 24 host to create (e.g.) shred.ing to create an internet hosted radio station.
# TODO
- [X] fix issue where streams HTML player disconnects after an hour
- [X] create fallback streams to support switching casters
- [-] build guided installer
- [ ] write a tutoral
# Quick Start
git clone https://code.bru.st/corwin/shred.ing-server.git
apt-get install icecast2
make install
links localhost:8000
# ABSTRACT
Here you will hopefully find everything (aside hardware and content) that you need to get started self-hosting a radio-station. This guide (and the other files in this repository) are focused on creating an internet hosted station but they should work well (albe it, with some adaptions) for creating a station on a private network ("intranet"), too.
This repository is intended to contain enough information to get started self-hosting an internet radio station. These same instructions should work fairly well for a intranet (private network) based station however some adaptions (such as skipping the "selecting a VPS provider" section) might be advisable. This setup can be a step toward broad-casting over "open" airwaves (however, this generally requires understanding local licesnsing and other applicable governance requirements, etc, which please as you may be inclined ). Additionally, these instructions (the software used here, specifically) should work well use with sophisticaed hardware (lots of analog and/or digital sources, etc.). That said, this is a "getting started" guide as well as instructions for useing the files in this repository. Patches welcome :)
# DESIGN
The radio station consists of two parts: the workstation, responsible for broadcasting to the server, and the server responsible for streaming the audio sigal to the devices of whomever may be listening. This repository includes most the configuration, HTML palyer, and other scripting needed for the server however the guild (this file!) contains some tips for working with Mixxx, the recommended broadcaster software.
## workstation
Although generally compatable in principle with a wide variety of software, the workstation setup required by this guild consists of a program (Mixxx, GPLv2+). Mixxx is available in the form of pre-compiled binaries for a variety of operating systems.
## server
The server is somewhat more complex, but ultimatly relies mainly on two programs (Icecast2, GPLv2; Nginx, BSD-2c): Icecast2 (GPLv2) which receives our mixed signal and streams it to listeners, and nginx (BSD-2c) which is a web-server which will "proxy" the MP3 stream produced by Icecast, not witstanding the underlying (nominally) Ubuntu 24 system software. Since this guide assumes we are building a internet facing server we also cover setting up ufw (a firewall program) and fail2ban (a nusance deturant toolkit), most in the form of explain how to use (and/or adapt!) the same filter rules in this repo.
## summary
* This file plus the contents of this repository, along with PC capable running some recent version of Ubuntu (nearly any PC), should be enough to get a simple network radio station online.
* This guild is covers the steps to aquire an virtual private server from internet provider and configure it to server your radio station on the internet.
* Alternately, these steps should work fairly well starting with old PC you might like to use to create an intranet station.
* This guild doesn't give a great deal of coverage to Mixxx (and none to other audio caster software):
* Get comfortable with Mixx (or the audio caster software of your choice) before building your own station.
* For example, be comforable playing and switching tracks, creating playlists, keying your mic, whatever you plan to do.
* This guide recommends Mixxx: install it and mess around!
* If you hate Mixxx good search terms could be "audiocaster software send to icecast2".
* There are some tips for getting started with Mixxx in the next section and elsewhere below.
# Materials
>>>>>>> Stashed changes

View File

@ -0,0 +1,9 @@
[Unit]
Description=hit service
After=network-online.target
[Service]
ExecStart=/opt/shred.ing/sbin/update-title-json.sh
[Install]
WantedBy=multi-user.target

View File

@ -0,0 +1,3 @@
#!/usr/bin/bash
tail -1 /var/log/icecast2/playlist.log | cut -d \| -f 4 | perl -nle 'printf qq({ "title" : "%s" }\n), $_'

View File

@ -0,0 +1,5 @@
#!/usr/bin/sh
while true ; do
su shred-batch -c '/opt/shred.ing/sbin/icecast-track-title-to-javascript.sh > /opt/shred.ing/js/title.json' ;
sleep 17
done

View File

@ -0,0 +1,8 @@
#!/usr/bin/sh
while true ; do
ls -l /opt/shred.ing/js/title.json;
#ps -f 75362;
systemctl status icecast-title.service
cat /opt/shred.ing/js/title.json;
sleep 10;
done

View File

@ -0,0 +1,65 @@
server {
listen 80;
listen [::]:80;
server_name shred.ing;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location / {
proxy_pass http://localhost:8000/live.mp3;
}
location ~ play(er)?|html?$ {
root /www/shred.ing;
try_files $1 /shred.html =404;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
}
location ~ js(on)?$ {
root /opt/shred.ing/js;
try_files $1 /title.json =404;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
}
}
server {
server_name shred.ing;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
listen [::]:443 ssl; # managed by Certbot
listen 443 ssl; # managed by Certbot
ssl_certificate /etc/letsencrypt/live/shred.ing/fullchain.pem; # managed by Certbot
ssl_certificate_key /etc/letsencrypt/live/shred.ing/privkey.pem; # managed by Certbot
include /etc/letsencrypt/options-ssl-nginx.conf; # managed by Certbot
ssl_dhparam /etc/letsencrypt/ssl-dhparams.pem; # managed by Certbot
location / {
proxy_pass http://localhost:8000/live.mp3;
}
location ~ play(er)?|html?$ {
root /www/shred.ing;
try_files $1 /shred.html =404;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
}
location ~ js(on)?$ {
root /opt/shred.ing/js;
try_files $1 /title.json =404;
# kill cache
add_header Last-Modified $date_gmt;
add_header Cache-Control 'no-store, no-cache';
if_modified_since off;
expires off;
etag off;
}
}

View File

@ -1,55 +1,36 @@
<icecast> <icecast>
<!-- location and admin are two arbitrary strings that are e.g. visible <location>The Island of Misfit Toys</location>
on the server info page of the icecast web interface <admin>yo@mammy</admin>
(server_version.xsl). -->
<location>Earth</location>
<admin>icemaster@localhost</admin>
<!-- IMPORTANT!
Especially for inexperienced users:
Start out by ONLY changing all passwords and restarting Icecast.
For detailed setup instructions please refer to the documentation.
It's also available here: http://icecast.org/docs/
-->
<limits> <limits>
<clients>100</clients> <clients>17q000</clients>
<sources>2</sources> <sources>17</sources>
<queue-size>524288</queue-size> <queue-size>524288</queue-size>
<client-timeout>30</client-timeout> <client-timeout>30</client-timeout>
<header-timeout>15</header-timeout> <header-timeout>15</header-timeout>
<source-timeout>10</source-timeout> <source-timeout>17</source-timeout>
<!-- If enabled, this will provide a burst of data when a client <!-- If enabled, this will provide a burst of data when a client
first connects, thereby significantly reducing the startup first connects, thereby significantly reducing the startup
time for listeners that do substantial buffering. However, time for listeners that do substantial buffering. However,
it also significantly increases latency between the source it also significantly increases latency between the source
client and listening client. For low-latency setups, you client and listening client. For low-latency setups, you
might want to disable this. --> might want to disable this. -->
<burst-on-connect>1</burst-on-connect> <!-- <burst-on-connect>1</burst-on-connect> -->
<!-- same as burst-on-connect, but this allows for being more <!-- same as burst-on-connect, but this allows for being more
specific on how much to burst. Most people won't need to specific on how much to burst. Most people won't need to
change from the default 64k. Applies to all mountpoints --> change from the default 64k. Applies to all mountpoints -->
<burst-size>65535</burst-size> <!-- <burst-size>65535</burst-size> -->
</limits> </limits>
<authentication> <authentication>
<!-- Sources log in with username 'source' --> <!-- Sources log in with username 'source' -->
<source-password>shredsourcepassword</source-password> <source-password>@@Source_Pass@@</source-password>
<!-- Relays log in with username 'relay' --> <!-- Relays log in with username 'relay' -->
<relay-password>shredrelaypassword</relay-password> <relay-password>@@Relay_Pass@@</relay-password>
<!-- Admin logs in with the username given below --> <!-- Admin logs in with the username given below -->
<admin-user>admin</admin-user> <admin-user>@@Admin_User@@</admin-user>
<admin-password>shredadminpassword</admin-password> <admin-password>@@Admin_Pass@@</admin-password>
</authentication> </authentication>
<!-- set the mountpoint for a shoutcast source to use, the default if not
specified is /stream but you can change it here if an alternative is
wanted or an extension is required
<shoutcast-mount>/live.nsv</shoutcast-mount>
-->
<!-- Uncomment this if you want directory listings -->
<!-- <!--
<directory> <directory>
<yp-url-timeout>15</yp-url-timeout> <yp-url-timeout>15</yp-url-timeout>
@ -57,41 +38,14 @@
</directory> </directory>
--> -->
<!-- This is the hostname other people will use to connect to your server. <hostname>@@hostname@@</hostname>
It affects mainly the urls generated by Icecast for playlists and yp
listings. You MUST configure it properly for YP listings to work!
-->
<hostname>shred.ing</hostname>
<!-- You may have multiple <listen-socket> elements -->
<listen-socket> <listen-socket>
<port>8000</port> <port>@@icecast2_port@@</port>
<!-- <bind-address>127.0.0.1</bind-address> -->
<!-- <shoutcast-mount>/stream</shoutcast-mount> -->
</listen-socket> </listen-socket>
<!--
<listen-socket>
<port>8080</port>
</listen-socket>
-->
<!--
<listen-socket>
<port>8443</port>
<ssl>1</ssl>
</listen-socket>
-->
<!-- Global header settings
Headers defined here will be returned for every HTTP request to Icecast.
The ACAO header makes Icecast public content/API by default
This will make streams easier embeddable (some HTML5 functionality needs it).
Also it allows direct access to e.g. /status-json.xsl from other sites.
If you don't want this, comment out the following line or read up on CORS.
-->
<http-headers> <http-headers>
<header name="Access-Control-Allow-Origin" value="*" /> <header name="Access-Control-Allow-Origin" value="*" />
<header name="X-Clacks-Overhead" value="GNU Terry Pratchett" />
</http-headers> </http-headers>
@ -103,12 +57,10 @@
<!--<master-server-port>8001</master-server-port>--> <!--<master-server-port>8001</master-server-port>-->
<!--<master-update-interval>120</master-update-interval>--> <!--<master-update-interval>120</master-update-interval>-->
<!--<master-password>hackme</master-password>--> <!--<master-password>hackme</master-password>-->
<!-- setting this makes all relays on-demand unless overridden, this is <!-- setting this makes all relays on-demand unless overridden, this is
useful for master relays which do not have <relay> definitions here. useful for master relays which do not have <relay> definitions here.
The default is 0 --> The default is 0 -->
<!--<relays-on-demand>1</relays-on-demand>--> <!--<relays-on-demand>1</relays-on-demand>-->
<!-- <!--
<relay> <relay>
<server>127.0.0.1</server> <server>127.0.0.1</server>
@ -122,38 +74,152 @@
--> -->
<!-- Mountpoints <!-- the default mount is the top of the fall-back chain
Only define <mount> sections if you want to use advanced options, anyone listening to something pushing to the default
like alternative usernames or passwords gets seomthing in our fallback chain, isntead.
-->
<!-- Default settings for all mounts that don't have a specific <mount type="normal">.
--> -->
<mount type="default"> <mount type="default">
<public>0</public> <public>0</public>
<!-- <intro>/server-wide-intro.ogg</intro> --> <!-- <intro>/server-wide-intro.ogg</intro> -->
<max-listener-duration>3600</max-listener-duration> <!-- <max-listener-duration>3600</max-listener-duration> -->
<username>live</username> <username>live</username>
<password>shredlivepassword</password> <password>@@Source_Pass@@</password>
<authentication type="url"> <fallback-override>1</fallback-override>
<option name="mount_add" value="http://auth.example.org/stream_start.php"/> <fallback-mount>/live</fallback-mount>
</authentication>
<http-headers>
<header name="foo" value="bar" />
</http-headers>
</mount> </mount>
<!-- <mount> --> <mount type="normal">
<!-- <mount-name>/black.mp3</mount-name> --> <public>0</public>
<!-- <fallback-mount>/silver.mp3</fallback-mount> --> <mount-name>/live.ogg</mount-name>
<!-- <fallback-override>1</fallback-override> --> <username>live</username>
<!-- </mount> --> <password>@@Source_Pass@@</password>
<fallback-override>1</fallback-override>
<fallback-mount>/gold.ogg</fallback-mount>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/gold.ogg</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/purple.ogg</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/purple.ogg</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/black.ogg</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/black.ogg</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/silver.ogg</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<!-- <intro>/server-wide-intro.ogg</intro> -->
<!-- <max-listener-duration>3600</max-listener-duration> -->
<mount-name>/silver.ogg</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/live.ogg</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<!-- <mount> --> <mount type="normal">
<!-- <mount-name>/silver.mp3</mount-name> --> <public>0</public>
<!-- <fallback-mount>/black.mp3</fallback-mount> --> <fallback-override>1</fallback-override>
<!-- <fallback-override>1</fallback-override> --> <mount-name>/live</mount-name>
<!-- </mount> --> <username>live</username>
<password>@@Source_Pass@@</password>
<fallback-override>1</fallback-override>
<fallback-mount>/live.mp3</fallback-mount>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/live.mp3</mount-name>
<username>live</username>
<password>@@Source_Pass@@</password>
<fallback-override>1</fallback-override>
<fallback-mount>/gold</fallback-mount>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/gold</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/gold.mp3</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/gold.mp3</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/gold.ogg</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/purple</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/purple.mp3</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/purple.mp3</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/black</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<!-- <intro>/server-wide-intro.ogg</intro> -->
<!-- <max-listener-duration>3600</max-listener-duration> -->
<mount-name>/black</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/black.mp3</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/black.mp3</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/silver</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<!-- <intro>/server-wide-intro.ogg</intro> -->
<!-- <max-listener-duration>3600</max-listener-duration> -->
<mount-name>/silver</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/silver.mp3</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<mount type="normal">
<public>0</public>
<mount-name>/silver.mp3</mount-name>
<fallback-override>1</fallback-override>
<fallback-mount>/live</fallback-mount>
<username>live</username>
<password>@@Source_Pass@@</password>
</mount>
<!-- Normal mounts --> <!-- Normal mounts -->
<!-- <!--
@ -202,16 +268,10 @@
<fileserve>1</fileserve> <fileserve>1</fileserve>
<paths> <paths>
<!-- basedir is only used if chroot is enabled -->
<basedir>/usr/share/icecast2</basedir>
<!-- Note that if <chroot> is turned on below, these paths must both
be relative to the new root, not the original root -->
<logdir>/var/log/icecast2</logdir> <logdir>/var/log/icecast2</logdir>
<webroot>/usr/share/icecast2/web</webroot> <webroot>/usr/share/icecast2/web</webroot>
<adminroot>/usr/share/icecast2/admin</adminroot> <adminroot>/usr/share/icecast2/admin</adminroot>
<!-- <pidfile>/usr/share/icecast2/icecast.pid</pidfile> --> <!-- <pidfile>/usr/share/icecast2/icecast.pid</pidfile> -->
<!-- Aliases: treat requests for 'source' path as being for 'dest' path <!-- Aliases: treat requests for 'source' path as being for 'dest' path
May be made specific to a port or bound address using the "port" May be made specific to a port or bound address using the "port"
and "bind-address" attributes. and "bind-address" attributes.
@ -233,7 +293,7 @@
<logging> <logging>
<accesslog>access.log</accesslog> <accesslog>access.log</accesslog>
<errorlog>error.log</errorlog> <errorlog>error.log</errorlog>
<!-- <playlistlog>playlist.log</playlistlog> --> <playlistlog>playlist.log</playlistlog>
<loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error --> <loglevel>3</loglevel> <!-- 4 Debug, 3 Info, 2 Warn, 1 Error -->
<logsize>10000</logsize> <!-- Max size of a logfile --> <logsize>10000</logsize> <!-- Max size of a logfile -->
<!-- If logarchive is enabled (1), then when logsize is reached <!-- If logarchive is enabled (1), then when logsize is reached

125
play.html Normal file
View File

@ -0,0 +1,125 @@
<!DOCTYPE html>
<html>
<head>
<script src="https://code.jquery.com/jquery.min.js"></script>
<script>
function play() {
var my = document.getElementById('player');
my.currentTime = 0;
/**this one loops***/
if ($('.btn').find('span i').hasClass('fa-stop')) {
$('.btn').find('span i').removeClass('fa-stop').addClass('fa-play-circle');
$('.texto').text("Play");
}
if (my.paused) {
my.play();
} else {
my.pause();
my.currentTime = 0;
}
my.addEventListener("ended", function(e) {
$('.btn').find('span i').removeClass('fa-play-circle').addClass('fa-stop');
$('.texto').text("Stop");
my.play();
});
}
async function updateTitle() {
const url = "/json";
try {
const response = await fetch(url);
if (!response.ok) {
//throw new Error(`Response status: ${response.status}`);
} else {
const json = await response.json();
//console.log(json);
document.getElementById('title').innerText = json.title;
}
} catch (error) {
console.error(error.message);
}
}
// document.shredIng = { title: "" };
// function updateTitle() {
// fetch("/js", {
// headers: { 'Content-Type': 'application/json'
// }
// }).then(function(response) {
// if(!response.ok) {
// // oh no, an error. better do stuff!
// }
// else {
// if(response.json && response.json.title) {
// document.shredIng.title = response.json.title;
// document.getElementById('title').innerText = document.shredIng.title;
// }
// }
// });
// }
setInterval(updateTitle, 7200);
</script>
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link href="https://fonts.googleapis.com/css2?family=Rubik+80s+Fade&display=swap" rel="stylesheet">
<style>
html {
background: #000;
padding: 0px;
margin: 0px;
border: 0px;
}
.fb {
width: 100%;
height: 100%;
min-height: 90vh;
}
.np {
padding: 0px;
margin: 0px;
border: 0px;
}
.bg {
background: #000;
background-image: linear-gradient(180deg,rgba(117, 117, 117, 1) 0%, rgba(110, 110, 110, 1) 17%, rgba(20, 20, 20, 1) 100%);
}
.rubik-80s-fade-regular {
font-family: "Rubik 80s Fade", system-ui;
font-weight: 400;
font-style: normal;
}
h1, h2, h3, h4, h5 {
font-family: "Rubik 80s Fade", system-ui;
font-weight: 400;
font-style: normal;
}
</style>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width">
<title>Shred.ing</title>
</head>
<body>
<div class="bg padding fb"><div style="text-align: center">
<div class=="np bg" id="music" style="padding-left:.5rem;">
<h1 style="font-size: 500%" class="np">Shred.ing</h1>
<h2 style="font-size: 330%" class="np">Internet Radio</h2>
<button type="button" class="btn btn-success" data-toggle="collapse" data-target="#player" onclick="play();">
<!-- <span><i class="fa fa-play-circle" aria-hidden="true"></i>
&nbsp;&nbsp;
<span class="texto">Play</span>
</span> -->
<audio controls id="player" class="collapse">
<source src="https://shred.ing" type="audio/mp3">
</audio>
</button>
<div id="title" style="border: 1px dashed cyan; font-family: Arial, Helvetica, sans-serif; font-size: 150%; padding: .75rem; margin-top: .5rem; text-align: center; padding-top: .75rem; color: #3c3; filter: drop-shadow(1.6px 1.3px .25px #c3c);" class="">
$Title$
</div></div>
</div>
<script> updateTitle(); </script>
</body>
</html>