<?xml version="1.0" encoding="UTF-8"?><rss version="2.0"
	xmlns:content="http://purl.org/rss/1.0/modules/content/"
	xmlns:wfw="http://wellformedweb.org/CommentAPI/"
	xmlns:dc="http://purl.org/dc/elements/1.1/"
	xmlns:atom="http://www.w3.org/2005/Atom"
	xmlns:sy="http://purl.org/rss/1.0/modules/syndication/"
	xmlns:slash="http://purl.org/rss/1.0/modules/slash/"
	>

<channel>
	<title>icecast &#8211; Blog of Kliment Andreev &#8211; A place so I won&#039;t forget things</title>
	<atom:link href="https://blog.andreev.it/tag/icecast/feed/" rel="self" type="application/rss+xml" />
	<link>https://blog.andreev.it</link>
	<description></description>
	<lastBuildDate>Thu, 05 Nov 2020 17:44:40 +0000</lastBuildDate>
	<language>en-US</language>
	<sy:updatePeriod>
	hourly	</sy:updatePeriod>
	<sy:updateFrequency>
	1	</sy:updateFrequency>
	
	<item>
		<title>AWS, CentOS: Create your own radio station and deploy it on Alexa (optional)</title>
		<link>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/</link>
					<comments>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/#comments</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 06 Oct 2019 12:55:24 +0000</pubDate>
				<category><![CDATA[AWS]]></category>
		<category><![CDATA[CentOS]]></category>
		<category><![CDATA[Cloud]]></category>
		<category><![CDATA[Editor's Pick]]></category>
		<category><![CDATA[Alexa]]></category>
		<category><![CDATA[centos]]></category>
		<category><![CDATA[icecast]]></category>
		<category><![CDATA[radio station]]></category>
		<guid isPermaLink="false">https://blog.andreev.it/?p=5735</guid>

					<description><![CDATA[In this post I&#8217;ll explain how to run your own Internet radio station. On&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll explain how to run your own Internet radio station. On top of that, I&#8217;ll show you how to build an Alexa skill to deploy your station in the cloud, so anyone with Alexa can say: &#8220;<strong>Alexa, play</strong> &lt;<strong>whatever_station</strong>&gt;&#8221; and hear your Internet radio station. There are many tutorials that describe this, but most of them require that you pay for some radio station hosting. If that&#8217;s easier for you, then you can skip this tutorial. If you want to have your own radio on the Internet (build from scratch) and deploy it or not on Alexa, than this guide is for you. There are some prerequisites though. You&#8217;ll need a CentOS 7 instance with a public IP and if you decide to publish it on Alexa, then you&#8217;ll need valid SSL certificate.<br />
<strong>IMPORTANT</strong>: If your want your station to be public on Alexa, then you also need license for the music. But no worries, if you just want your music played over Alexa at home (and not public), you can still do it.<br />
Also, the ices2 client can&#8217;t stream MP3 files, so you have to convert them to OGG. If you want to stream MP3s directly, check the earlier version of ices. The configuration file is almost identical. Ogg-Vorbis is perfectly fine and I&#8217;ll show you how to convert your library from MP3 to OGG.<br />
So, there are two parts of this tutorial. Build your own station first and then the optional Alexa part. Let&#8217;s start.</p>
<h1>CentOS 7</h1>
<p>Make sure you have a CentOS 7 instance ready in the cloud with a public IP. NATed instances are OK as long as you can control the ports. In addition, make sure SELinux is disabled. Disabling the OS firewall is optional. I assume that the firewall on the instance is on.</p>
<h2>icecast</h2>
<p><a href="http://icecast.org/" rel="noopener noreferrer" target="_blank">icecast </a>is a streaming <strong>server </strong>similar to shoutcast. It broadcasts a stream that&#8217;s provided by a source. The source can be a microphone, sound card or a file. In order to stream a file, we need a <strong>client </strong>such as ices, LiquidSoap etc. You are not the client, you are the <strong>listener</strong>. Now that we have this straighten up, let&#8217;s install icecast from the source. Just copy and paste everything below. If you get an error, execute line by line and see where it fails. Fix any issues, if any.</p>
<pre class="brush: bash; highlight: [7]; title: ; notranslate">
yum -y groupinstall &quot;Development Tools&quot;
yum install -y wget curl-devel libvorbis-devel libxslt-devel libxslt-devel openssl-devel
cd /tmp
wget http://downloads.xiph.org/releases/icecast/icecast-2.4.4.tar.gz
tar xzvf icecast-2.4.4.tar.gz
cd icecast-2.4.4
./configure --prefix=/opt/icecast --with-curl --with-openssl
make
make install
</pre>
<p>We specified <strong>/opt/icecast</strong> where <strong>icecast </strong>will be installed (line 7). You can change that in the <strong>configure </strong>statement, but you have to adjust everything after. <strong>icecast </strong>creates a sample configuration file named <strong>icecast.xml</strong> under <strong>/opt/icecast/etc</strong>. If you look at the XML file, you&#8217;ll see a bunch of settings and some of them have to be changed.<br />
We&#8217;ll change the following parameters:</p>
<ul>
&lt;<strong>location</strong>&gt; &#8211; Specifies where the radio station is located.<br />
&lt;<strong>admin</strong>&gt; &#8211; An e-mail address of the radio station admin.<br />
&lt;<strong>hostname</strong>&gt; &#8211; A hostname for the radio station. Important only if you are publishing this in YP directory of broadcast streams. Irrelevant for us, but we&#8217;ll change it anyway.<br />
&lt;<strong>source-password</strong>&gt; &#8211; A password so the source client can attach to the icecast server and provide the OGG stream.<br />
&lt;<strong>relay-password</strong>&gt; &#8211; Used only if the icecast is used as a relay.<br />
&lt;<strong>admin-user</strong>&gt; &#8211; Username for the admin web page.<br />
&lt;<strong>admin-password</strong>&gt; &#8211; Password for the admin user.<br />
&lt;<strong>logdir</strong>&gt; &#8211; A directory where the log file is stored.<br />
&lt;<strong>user</strong>&gt; &#8211; A local OS user that will run icecast. Don&#8217;t use root, it won&#8217;t work. You can create a separate user for this. The built-in nobody user is perfectly fine.<br />
&lt;<strong>group</strong>&gt; &#8211; A local OS group that will run icecast. </ul>
<p>So, let&#8217;s change these manually or with this code. Adjust to suit your needs.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/icecast/etc
location=&quot;New Jersey&quot;
admin=klimenta@iandreev.com
hostname=radio.iandreev.com
sourcepwd=Sup3rStr0ngP455w0rd
relaypwd=Sup3rStr0ngP455w0rd
admin=myusername
password=Sup3rStr0ngP455w0rd
logdir=/opt/icecast/log
user=nobody
group=nobody
sed -i &quot;s/&lt;location&gt;Earth/&lt;location&gt;$location/&quot; icecast.xml
sed -i &quot;s/&lt;admin&gt;icemaster@localhost/&lt;admin&gt;$admin/&quot; icecast.xml
sed -i &quot;s/&lt;hostname&gt;localhost/&lt;hostname&gt;$hostname/&quot; icecast.xml
sed -i &quot;s/&lt;source-password&gt;hackme/&lt;source-password&gt;$sourcepwd/&quot; icecast.xml
sed -i &quot;s/&lt;relay-password&gt;hackme/&lt;relay-password&gt;$relaypwd/&quot; icecast.xml
sed -i &quot;s/&lt;admin-user&gt;admin/&lt;admin-user&gt;$admin/&quot; icecast.xml
sed -i &quot;s/&lt;admin-password&gt;hackme/&lt;admin-password&gt;$password/&quot; icecast.xml
sed -i &quot;s|&lt;logdir&gt;/opt/icecast/var/log/icecast|&lt;logdir&gt;$logdir|&quot; icecast.xml
sed -i &quot;s/&lt;user&gt;nobody/&lt;user&gt;$user/&quot; icecast.xml
sed -i &quot;s/&lt;group&gt;nogroup/&lt;group&gt;$group/&quot; icecast.xml
</pre>
<p>Let&#8217;s create the log directory and give the icecast daemon permission to write.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/icecast
mkdir log
chown -R nobody:nobody log
</pre>
<p>icecast doesn&#8217;t come with a startup systemd script, so we&#8217;ll have to take care of that.</p>
<pre class="brush: bash; title: ; notranslate">
cat &lt;&lt; EOF &gt; /etc/systemd/system/icecast.service
&#x5B;Unit]
Description=Icecast Network Audio Streaming Server
After=network.target

&#x5B;Service]
Type=simple
User=nobody
Group=nobody
ExecStart=/opt/icecast/bin/icecast -c /opt/icecast/etc/icecast.xml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID

&#x5B;Install]
WantedBy=multi-user.target
EOF
</pre>
<p>Now you can enable icecast on boot and start it.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable icecast
systemctl start icecast
</pre>
<p>If you run <strong>systemctl status icecast</strong>, you should see that the daemon is running. Also, if you check the error log (<strong>tail /opt/icecast/log/error.log</strong>), you&#8217;ll see that the daemon is running too. Ignore the SSL warning for now.<br />
<strong>icecast </strong>runs on port <strong>8000</strong>, so if you have a firewall, you&#8217;ll have to poke a hole.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=8000/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
<p>From a browser, go to your server&#8217;s IP on port 8000, e.g. <strong>http://201.202.203.24:8000</strong> and you&#8217;ll see something like this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg"><img fetchpriority="high" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg" alt="" width="929" height="248" class="aligncenter size-full wp-image-8683" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-01.jpg 929w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-300x80.jpg 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-768x205.jpg 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-01-585x156.jpg 585w" sizes="(max-width: 929px) 100vw, 929px" /></a><br />
If you click on <strong>Administration</strong>, you&#8217;ll be prompted to log in. Use the <strong>admin-user</strong> and <strong>admin-password</strong> that you&#8217;ve changed in <strong>icecast.xml</strong>.</p>
<h2>ices</h2>
<p>Now that we have the streaming server ready, let&#8217;s install the source client. The source client will provide the stream to the server. We&#8217;ll use <strong>ices </strong>for this.</p>
<pre class="brush: bash; title: ; notranslate">
cd /tmp
wget http://downloads.us.xiph.org/releases/ices/ices-2.0.2.tar.gz
tar xzvf ices-2.0.2.tar.gz
cd ices-2.0.2
yum -y install libshout-devel
./configure --prefix=/opt/ices
make &amp;&amp; make install
</pre>
<p><strong>ices </strong>creates some configuration files under <strong>/opt/ices/share/ices</strong>. We&#8217;ll use the <strong>ices-playlist.xml</strong> template to generate a radio station for us.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices/share/ices
cp ices-playlist.xml myradio.xml
</pre>
<p>Now edit <strong>myradio.xml</strong> and change the following with a script or manually. Make sure you adjust for your needs.</p>
<ul>
&lt;<strong>logpath</strong>&gt; &#8211; A directory where the log file is stored.<br />
&lt;<strong>logfile</strong>&gt; &#8211; The name of the log file.<br />
&lt;<strong>name</strong>&gt; &#8211; The name of your radio.<br />
&lt;<strong>genre</strong>&gt; &#8211; Self explanatory.<br />
&lt;<strong>description</strong>&gt; &#8211; Enter a description for your station.<br />
&lt;<strong>password</strong>&gt; &#8211; The password to connect to icecast. It should match the <strong>source-password in icecast.xml</strong><br />
&lt;<strong>mount</strong>&gt; &#8211; The name of the mount. This is like the URL for your station. It has to end with .ogg<br />
&lt;<strong>param name</strong>&gt; &#8211; A file where the playlist for this station will be stored.
</ul>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices/share/ices
logpath=/opt/ices/log
logfile=myradio.log
name=&quot;Radio MyRadio&quot;
genre=&quot;Some genre&quot;
descr=&quot;Best Radio in the Universe and beyond&quot;
pwd=Sup3rStr0ngP455w0rd
mount=/myradio.ogg
playlist=/opt/ices/playlists/myradio.txt
sed -i &quot;s|&lt;logpath&gt;/var/log/ices|&lt;logpath&gt;$logpath|&quot; myradio.xml
sed -i &quot;s/&lt;logfile&gt;ices.log/&lt;logfile&gt;$logfile/&quot; myradio.xml
sed -i &quot;s/&lt;name&gt;Example stream name/&lt;name&gt;$name/&quot; myradio.xml
sed -i &quot;s/&lt;genre&gt;Example genre/&lt;genre&gt;$genre/&quot; myradio.xml
sed -i &quot;s/&lt;description&gt;A short description of your stream/&lt;description&gt;$descr/&quot; myradio.xml
sed -i &quot;s/&lt;password&gt;hackme/&lt;password&gt;$pwd/&quot; myradio.xml
sed -i &quot;s|&lt;mount&gt;/example1.ogg|&lt;mount&gt;$mount|&quot; myradio.xml
sed -i &quot;s|&lt;param name=\&quot;file\&quot;&gt;playlist.txt|&lt;param name=\&quot;file\&quot;&gt;$playlist|&quot; myradio.xml
</pre>
<p>Let&#8217;s create two directories. One for the logs and the other for the files where we&#8217;ll store playlists. Not the music files  &#8211; the playlists. The playlists tells ices where to look for your music.</p>
<pre class="brush: bash; title: ; notranslate">
cd /opt/ices
mkdir log playlists
chown -R nobody:nobody log
</pre>
<p>Create a startup script for ices as well. Look at line 10, that&#8217;s where my config file for this radio station is. Can you have multiple radio stations? Absolutely, one <strong>icecast </strong>server can handle multiple <strong>ices </strong>clients. Just invoke another <strong>ices </strong>client with a different config file and create a similar startup script with a different URL, e.g &#8230;/myradio.ogg and &#8230;/mystation.ogg. </p>
<pre class="brush: bash; highlight: [10]; title: ; notranslate">
cat &lt;&lt; EOF &gt; /etc/systemd/system/ices-myradio.service
&#x5B;Unit]
Description=Ices Network Audio Streaming Client for MyRadio
After=icecast.service

&#x5B;Service]
Type=simple
User=nobody
Group=nobody
ExecStart=/opt/ices/bin/ices /opt/ices/share/ices/myradio.xml
ExecReload=/bin/kill -HUP $MAINPID
ExecStop=/bin/kill $MAINPID
Restart=always
RestartSec=3

&#x5B;Install]
WantedBy=multi-user.target
EOF
</pre>
<p>At this point you can tell ices where your music is stored. As I mentioned earlier, we&#8217;ll have to use Ogg-Vorbis files (.ogg).<br />
So, create a folder where your music files will be stored. I&#8217;ll use <strong>/music/myradio</strong> and put some public royalty free music there.<br />
Courtesy of <strong>Patrick de Arteaga</strong>. Please give him some credit and visit his <a href="https://patrickdearteaga.com/" rel="noopener noreferrer" target="_blank">site</a>.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir -p /music/myradio
cd /music/myradio
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Battleship.ogg
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Chiptronical.ogg
wget -U 'Mozilla/5.0 (X11; U; Linux i686; en-US; rv:1.8.1.6) Gecko/20070802 SeaMonkey/1.1.4' https://patrickdearteaga.com/audio/Friends.ogg
</pre>
<p>OK, so we have our music there. When the stream ends, it will start from the first song. Now we have to create the playlist and put it under the <strong>/opt/ices/conf</strong> directory.<br />
If you remember we specified <strong><param name></strong> directive in the <strong>myradio.xml</strong> file referencing where the playlist is stored. Let&#8217;s create the file.</p>
<pre class="brush: bash; title: ; notranslate">
ls -d /music/myradio/*.ogg &gt; /opt/ices/playlists/myradio.txt
</pre>
<p>Now that we have everything ready, let&#8217;s enable ices on boot and start it.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl enable ices-myradio
systemctl start ices-myradio
</pre>
<p>Check if everything is OK. </p>
<pre class="brush: bash; title: ; notranslate">
systemctl status ices-myradio
tail /opt/ices/log/myradio.log
</pre>
<p>If everything looks good, you can listen to your stream at <strong>http://whateverIP:8000/myradio.ogg</strong>.<br />
If you are satisfied with this, you can stop reading. You&#8217;ve accomplished enough. If you want to see how to convert MP3 to OGG and how to use a reverse proxy so you can stream over HTTP, then read further.</p>
<h2>MP3 to OGG conversion (sox)</h2>
<p>If you have a bunch of files that you want to convert to OGG, you can use sox. It&#8217;s a very powerful tool for conversion between formats. Let&#8217;s install it first.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install libmad-devel libid3tag-devel lame lame-devel flac flac-devel
cd /tmp
wget https://sourceforge.net/projects/sox/files/sox/14.4.2/sox-14.4.2.tar.gz
tar xvfz sox-14.4.2.tar.gz
cd sox-14.4.2
./configure
make -s &amp;&amp; make install
</pre>
<p>Now you can convert the files from MP3 to OGG and vice versa. Conversion is very simple. Just specify the source and the destination file, e.g. <strong>sox file1.mp3 file1.ogg</strong> and that&#8217;s it. If you want to see the details of the file such as bit-rate, duration, sample etc, use <strong>soxi</strong>, e.g. <strong>soxi file.ogg</strong>. If you want more details on sox and it&#8217;s usage, please check their <a href="http://sox.sourceforge.net/" rel="noopener noreferrer" target="_blank">website</a>. For <strong>soxi</strong>, check this <a href="http://sox.sourceforge.net/soxi.html" rel="noopener noreferrer" target="_blank">link</a>.<br />
If you want to convert your files in bulk, you can use this command that will search for MP3 files in your current directory and subdirectories and convert them to OGG.</p>
<pre class="brush: bash; title: ; notranslate">
find . -type f -name &quot;*.mp3&quot; -exec sh -c 'sox &quot;$1&quot; &quot;${1%.mp3}.ogg&quot;' _ {} \;
</pre>
<h2>nginx as reverse proxy</h2>
<p>If you recall, we are using <strong>http://somenameorip:8000/myradio.ogg</strong> to access our station. This is fine for regular users from their home, but in any corporate environment, anything than port 80 and 443 are blocked. So, we have to stream our music over port 80. I&#8217;ll show later how to stream over 443 because it&#8217;s a requirement for Alexa. First thing first, let&#8217;s install <strong>nginx</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
yum -y install nginx
systemctl enable nginx
</pre>
<p><strong>nginx </strong>creates a configuration file under <strong>/etc/nginx</strong> called <strong>nginx.conf</strong>. Look for this snippet in the configuration file.</p>
<pre class="brush: plain; title: ; notranslate">
        location / {
        }
</pre>
<p>&#8230;and change it to this.</p>
<pre class="brush: plain; title: ; notranslate">
        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }
</pre>
<p>You can start the reverse proxy now and verify that it&#8217;s working.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl start nginx
systemctl status nginx
</pre>
<p>Because we are proxying over HTTP, we have to open the firewall too and if you want you can close port 8000. It&#8217;s not needed anymore.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=80/tcp --zone=public --permanent
firewall-cmd --remove-port=8000/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
<p>Now you can access your station as <strong>http://serveriporname/myradio.ogg</strong>, so there is no need to specify port 8000.</p>
<h2>SSL certificate</h2>
<p>If you decide to publish your station on Alexa, you&#8217;ll need a valid (not self-signed) certificate. You will also need to reconfigure both nginx and icecast to use SSL. You can&#8217;t access your server by IP because you&#8217;ll get an invalid certificate warning, so make sure you have your DNS ready to resolve your IP to a valid domain name. Get the certificates ready. You&#8217;ll need the private key, the certificate, the intermediate certificate and the root CA certificate. When you purchase a certificate all of these will be available for you (except for the private key).<br />
Let&#8217;s create the chain. In my case, I have each certificate in a separate file and I&#8217;ll create one certificate for icecast.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /opt/icecast/ssl
cat star.iandreev.com.key star.iandreev.com.crt star.iandreev.com.inter star.iandreev.com.CA &gt; /opt/icecast/ssl/icecast.pem
chown -R nobody:nobody /opt/icecast/ssl
</pre>
<p>Then, edit <strong>/opt/icecast/etc/icecast.xml</strong> and find the following snippet.</p>
<pre class="brush: plain; title: ; notranslate">
    &lt;listen-socket&gt;
        &lt;port&gt;8000&lt;/port&gt;
    &lt;/listen-socket&gt;
</pre>
<p>Copy these three lines after, so the config looks like this now. We are telling <strong>icecast </strong>to spawn itself and listen on port 8443 too.</p>
<pre class="brush: bash; title: ; notranslate">
    &lt;listen-socket&gt;
        &lt;port&gt;8000&lt;/port&gt;
    &lt;/listen-socket&gt;
    &lt;listen-socket&gt;
        &lt;port&gt;8443&lt;/port&gt;
        &lt;ssl&gt;1&lt;/ssl&gt;
    &lt;/listen-socket&gt;
</pre>
<p>We also have to specify the certificate chain. Find this line in the config and <strong>uncomment it</strong>. Make sure you uncomment the line, it&#8217;s commented by default.</p>
<pre class="brush: bash; title: ; notranslate">
&lt;ssl-certificate&gt;/opt/icecast/ssl/icecast.pem&lt;/ssl-certificate&gt;
</pre>
<p>Now, you have to restart <strong>icecast</strong> and <strong>ices </strong>too.</p>
<pre class="brush: bash; title: ; notranslate">
systemctl restart icecast
systemctl restart ices-myradio
</pre>
<p>And if you check the error.log (yes, error log), you&#8217;ll see that icecast is using SSL now.</p>
<pre class="brush: bash; highlight: [1]; title: ; notranslate">
tail /opt/icecast/log/error.log
&#x5B;2019-10-03  22:29:23] INFO connection/get_ssl_certificate SSL certificate found at /opt/icecast/ssl/icecast.pem
&#x5B;2019-10-03  22:29:23] INFO connection/get_ssl_certificate SSL using ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES
</pre>
<p>Now the <strong>nginx </strong>part. Edit <strong>/etc/nginx/nginx.conf</strong>.</p>
<ul>
&#8211; uncomment the section for &#8220;a TLS enabled server&#8221;. Remove the # from all the lines below # Settings for a TLS enabled server. Don&#8217;t uncomment this line.<br />
&#8211; Replace location / {} with this snippet. Not the first occurrence for port 80. This is for port 443.</p>
<pre class="brush: plain; title: ; notranslate">
        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }
</pre>
<p>&#8211; Copy your private key to <strong>/etc/pki/nginx/private/server.key</strong> and your public certificate to <strong>/etc/pki/nginx/server.crt</strong>. If the directories do not exists, create them. (<strong>cd /etc/pki &#038;&#038; mkdir -p nginx/private</strong>). Also, make sure that the certificate (<strong>server.crt</strong>) is a chain of certificates, not just a simple server certificate. If you do <strong>curl -iv https://yoursite.com</strong>, the result for the SSL should be clear without any errors or warning. You can reuse the icecast.pem certificate for icecast and copy that file as <strong>server.crt</strong> under <strong>/etc/pki/nginx/</strong>. You can even reference the same file in the same directory, but then you have to change the config files and permissions.<br />
&#8211; Reload nginx (<strong>systemctl reload nginx</strong>)<br />
&#8211; Open the firewall</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --add-port=443/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
</ul>
<p>Now you can access your server with https. If you want to be even more precise, you can redirect the http to https, so you don&#8217;t have to specify the protocol in the browser. Edit <strong>nginx.conf</strong> and add line 5. Reload nginx after (<strong>systemctl reload nginx</strong>).</p>
<pre class="brush: bash; highlight: [5]; title: ; notranslate">
server {
    listen       80 default_server;
    listen       &#x5B;::]:80 default_server;
    server_name  _;
    return 301 https://$host$request_uri;
    root         /usr/share/nginx/html;
</pre>
<p>Here is my <strong>/etc/nginx/nginx.conf</strong> file.</p>
<pre class="brush: bash; collapse: true; light: false; title: ; toolbar: true; notranslate">
user nginx;
worker_processes auto;
error_log /var/log/nginx/error.log;
pid /run/nginx.pid;

# Load dynamic modules. See /usr/share/nginx/README.dynamic.
include /usr/share/nginx/modules/*.conf;

events {
    worker_connections 1024;
}

http {
    log_format  main  '$remote_addr - $remote_user &#x5B;$time_local] &quot;$request&quot; '
                      '$status $body_bytes_sent &quot;$http_referer&quot; '
                      '&quot;$http_user_agent&quot; &quot;$http_x_forwarded_for&quot;';

    access_log  /var/log/nginx/access.log  main;

    sendfile            on;
    tcp_nopush          on;
    tcp_nodelay         on;
    keepalive_timeout   65;
    types_hash_max_size 2048;

    include             /etc/nginx/mime.types;
    default_type        application/octet-stream;

    # Load modular configuration files from the /etc/nginx/conf.d directory.
    # See http://nginx.org/en/docs/ngx_core_module.html#include
    # for more information.
    include /etc/nginx/conf.d/*.conf;

    server {
        listen       80 default_server;
        listen       &#x5B;::]:80 default_server;
        server_name  _;
        return 301 https://$host$request_uri;
        root         /usr/share/nginx/html;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location / {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

# Settings for a TLS enabled server.

    server {
        listen       443 ssl http2 default_server;
        listen       &#x5B;::]:443 ssl http2 default_server;
        server_name  _;
        root         /usr/share/nginx/html;

        ssl_certificate &quot;/etc/pki/nginx/server.crt&quot;;
        ssl_certificate_key &quot;/etc/pki/nginx/private/server.key&quot;;
        ssl_session_cache shared:SSL:1m;
        ssl_session_timeout  10m;
        ssl_ciphers HIGH:!aNULL:!MD5;
        ssl_prefer_server_ciphers on;

        # Load configuration files for the default server block.
        include /etc/nginx/default.d/*.conf;

        location /myradio.ogg {
          proxy_set_header Host $host;
          proxy_set_header X-Real-IP $remote_addr;
          proxy_pass http://localhost:8000/myradio.ogg;
        }

        error_page 404 /404.html;
            location = /40x.html {
        }

        error_page 500 502 503 504 /50x.html;
            location = /50x.html {
        }
    }

}
</pre>
<h1>Alexa</h1>
<p>Now comes the fun part. It&#8217;s much easier than you think thanks to <a href="https://andymoore.info/" rel="noopener noreferrer" target="_blank">Andy Moore</a>. He has a great step-by-step <a href="https://andymoore.info/stream-your-radio-station-on-alexa-for-free" rel="noopener noreferrer" target="_blank">tutorial </a>for this. I&#8217;ll quickly go over what&#8217;s needed.<br />
You&#8217;ll need your:</p>
<ul>
&#8211; Radio station name<br />
&#8211; Name of your website<br />
&#8211; URL of your stream (it has to be https)</ul>
<p>Fun fact. I named my station Radio Andreev and whatever I said, Alexa couldn&#8217;t understand. It was looking for Radio Andrea all the time. So, do yourself a favor and name your station something that Alexa can understand. I named my station Radio Tincup, my website is https://radio.iandreev.com and the stream is at https://radio.iandreev.com/myradio.ogg. Generate the JSON from the site above or get this code and change your values. Click the (+) sign to expand. </p>
<pre class="brush: xml; collapse: true; highlight: [4,27,28,29,30]; light: false; title: ; toolbar: true; notranslate">
{
  &quot;interactionModel&quot;: {
    &quot;languageModel&quot;: {
      &quot;invocationName&quot;: &quot;radio tincup&quot;,
      &quot;intents&quot;: &#x5B;
        {
          &quot;name&quot;: &quot;AMAZON.CancelIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Cancel&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.HelpIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Help&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.StopIntent&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Stop&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;Play&quot;,
          &quot;samples&quot;: &#x5B;
            &quot;Ask radio tincup to play radio tincup&quot;,
            &quot;Start radio tincup&quot;,
            &quot;Ask radio tincup to play&quot;,
            &quot;Play radio tincup&quot;
          ]
        },
        {
          &quot;name&quot;: &quot;AMAZON.NavigateHomeIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        },
        {
          &quot;name&quot;: &quot;AMAZON.PauseIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        },
        {
          &quot;name&quot;: &quot;AMAZON.ResumeIntent&quot;,
          &quot;samples&quot;: &#x5B;]
        }
      ],
      &quot;types&quot;: &#x5B;]
    }
  }
}
</pre>
<p>And this is your JavaScript. </p>
<pre class="brush: jscript; collapse: true; highlight: [28,71]; light: false; title: ; toolbar: true; notranslate">
const Alexa = require('ask-sdk-core');
var https = require('https');

const PlayHandler = {
	canHandle(handlerInput)
	{
		return (
			handlerInput.requestEnvelope.request.type === 'LaunchRequest' ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'Play'
			) ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'AMAZON.ResumeIntent'
			)
		);
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder
			.addDirective({
				type: 'AudioPlayer.Play',
				playBehavior: 'REPLACE_ALL',
				audioItem:{
					stream:{
						token: '0',
						url: 'https://radio.iandreev.com/myradio.ogg',
						offsetInMilliseconds: 0
					}
				}
			})
			.getResponse();
	}
};

const PauseStopHandler = {
	canHandle(handlerInput)
	{
		return (
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				(
					handlerInput.requestEnvelope.request.intent.name === 'AMAZON.CancelIntent' ||
					handlerInput.requestEnvelope.request.intent.name === 'AMAZON.StopIntent'
				)
			) ||
			(
				handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
				handlerInput.requestEnvelope.request.intent.name === 'AMAZON.PauseIntent'
			);
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder
			.addDirective({
				type: 'AudioPlayer.ClearQueue',
				clearBehavior: 'CLEAR_ALL'
			})
			.getResponse();
	}
};

const HelpIntentHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'IntentRequest' &amp;&amp;
			handlerInput.requestEnvelope.request.intent.name === 'AMAZON.HelpIntent';
	},
	handle(handlerInput)
	{
		const speechText = 'You can say Play, Stop or Resume. For more information please visit radio dot iandreev dot com';
		return handlerInput.responseBuilder
			.speak(speechText)
			.getResponse();
	}
};

const SessionEndedRequestHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'SessionEndedRequest';
	},
	handle(handlerInput)
	{
		return handlerInput.responseBuilder.getResponse();
	}
};

const IntentReflectorHandler = {
	canHandle(handlerInput)
	{
		return handlerInput.requestEnvelope.request.type === 'IntentRequest';
	},
	handle(handlerInput)
	{
		const intentName = handlerInput.requestEnvelope.request.intent.name;
		const speechText = 'NO INTENT HELP TEXT';
		return handlerInput.responseBuilder
			.speak(speechText)
			.getResponse();
	}
};

const ErrorHandler = {
	canHandle()
	{
		return true;
	},
	handle(handlerInput, error)
	{
		const speechText = `Sorry, I could not understand what you said. Please try again.`;
		return handlerInput.responseBuilder
			.speak(speechText)
			.reprompt(speechText)
			.getResponse();
	}
};

exports.handler = Alexa.SkillBuilders.custom()
	.addRequestHandlers(
		PlayHandler,
		PauseStopHandler,
		HelpIntentHandler,
		SessionEndedRequestHandler,
		IntentReflectorHandler)
	.addErrorHandlers(
		ErrorHandler)
	.lambda();
</pre>
<p>Go to <a href="https://developer.amazon.com" rel="noopener noreferrer" target="_blank">https://developer.amazon.com</a> and log in (or register). Make sure you use the same account in the developer console with the same account that you have your Alexa configured. Once logged in, click on <strong>Alexa </strong>from the menu and then <strong>Alexa Skills Kit</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png" alt="" width="503" height="180" class="aligncenter size-full wp-image-8684" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-02.png 503w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-02-300x107.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></a><br />
Click <strong>Create Skill</strong>. Enter the name of your station and the language.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png"><img decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png" alt="" width="496" height="299" class="aligncenter size-full wp-image-8685" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-03.png 496w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-03-300x181.png 300w" sizes="(max-width: 496px) 100vw, 496px" /></a><br />
Choose a <strong>Custom </strong>model.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png" alt="" width="416" height="341" class="aligncenter size-full wp-image-8686" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-04.png 416w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-04-300x246.png 300w" sizes="(max-width: 416px) 100vw, 416px" /></a><br />
Choose <strong>Alexa-Hosted</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png" alt="" width="317" height="275" class="aligncenter size-full wp-image-8687" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-05.png 317w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-05-300x260.png 300w" sizes="(max-width: 317px) 100vw, 317px" /></a><br />
Scroll up and in the top right corner click <strong>Create skill</strong>. After 30-40 seconds, you&#8217;ll be presented with a different screen. On the left side find the <strong>Interfaces </strong>menu, then click on it. Then enable the <strong>Audio Player</strong> on the right.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png" alt="" width="1061" height="442" class="aligncenter size-full wp-image-8688" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-06.png 1061w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-300x125.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-1024x427.png 1024w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-768x320.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-06-585x244.png 585w" sizes="(max-width: 1061px) 100vw, 1061px" /></a><br />
Scroll up and click on <strong>Save Interfaces</strong> first, then <strong>Build Model</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png" alt="" width="634" height="182" class="aligncenter size-full wp-image-8689" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-07.png 634w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-07-300x86.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-07-585x168.png 585w" sizes="(max-width: 634px) 100vw, 634px" /></a><br />
Click on the <strong>JSON Editor</strong> on the left, right above <strong>Interfaces</strong>. Once the editor shows up, delete everything there and paste the JSON file.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png" alt="" width="771" height="204" class="aligncenter size-full wp-image-8690" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-08.png 771w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-300x79.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-768x203.png 768w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-08-585x155.png 585w" sizes="(max-width: 771px) 100vw, 771px" /></a><br />
Click <strong>Save Model</strong> then <strong>Build Model</strong> from the top.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png" alt="" width="597" height="156" class="aligncenter size-full wp-image-8691" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-09.png 597w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-09-300x78.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-09-585x153.png 585w" sizes="(max-width: 597px) 100vw, 597px" /></a><br />
Click on <strong>Code </strong>from the top and paste the <strong>JavaScript</strong> in the editor.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png" alt="" width="345" height="285" class="aligncenter size-full wp-image-8692" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-10.png 345w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-10-300x248.png 300w" sizes="(max-width: 345px) 100vw, 345px" /></a><br />
Click <strong>Save </strong>then <strong>Deploy </strong>from the upper right.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png" alt="" width="404" height="103" class="aligncenter size-full wp-image-8693" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-11.png 404w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-11-300x76.png 300w" sizes="(max-width: 404px) 100vw, 404px" /></a><br />
Click on <strong>Test</strong>, allow access to the microphone and change <strong>Skill testing</strong> from <strong>Off </strong>to <strong>Development</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png" alt="" width="527" height="220" class="aligncenter size-full wp-image-8694" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-12.png 527w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-12-300x125.png 300w" sizes="(max-width: 527px) 100vw, 527px" /></a><br />
Now, with the mouse hit the microphone icon, hold it there, say <strong>Alexa Play Radio Tincup</strong> and release the button.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png" alt="" width="503" height="273" class="aligncenter size-full wp-image-8695" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-13.png 503w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-13-300x163.png 300w" sizes="(max-width: 503px) 100vw, 503px" /></a><br />
Here is what I was talking to. Alexa can&#8217;t understand me&#8230;or maybe my mic is bad. If Alexa doesn&#8217;t understand, just type &#8220;<strong>Alexa play Radio Tincup</strong>&#8221; in the text field.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png" alt="" width="414" height="778" class="aligncenter size-full wp-image-8696" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-14.png 414w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-14-160x300.png 160w" sizes="(max-width: 414px) 100vw, 414px" /></a><br />
You won&#8217;t hear Alexa playing and you won&#8217;t hear any music from the computer, but you&#8217;ll see a JSON response with the valid URL of your OGG stream.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png" alt="" width="667" height="385" class="aligncenter size-full wp-image-8697" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-15.png 667w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-15-300x173.png 300w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-15-585x338.png 585w" sizes="(max-width: 667px) 100vw, 667px" /></a><br />
At this point you are all set. You can play your music using Alexa. For most people this would be the end. But if you really like to publish your station to be public, then proceed. In the console, click on <strong>Distribution</strong>.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg" alt="" width="508" height="102" class="aligncenter size-full wp-image-8698" srcset="https://blog.andreev.it/wp-content/uploads/2019/10/P137-16.jpg 508w, https://blog.andreev.it/wp-content/uploads/2019/10/P137-16-300x60.jpg 300w" sizes="(max-width: 508px) 100vw, 508px" /></a><br />
You&#8217;ll see a bunch of questions regarding the nature of your station, are you collecting customer information, are you advertising and so on. Just follow the process and hopefully, you&#8217;ll get your station published.</p>
<h1>Cleanup</h1>
<p>You might want to delete the tarballs for the icecast, ices and sox that you&#8217;ve downloaded.</p>
<pre class="brush: bash; title: ; notranslate">
rm -rf /tmp/ice*
rm -rf /tmp/sox*
</pre>
<p>You&#8217;ll also want to make sure that the firewall allows only port 80 and 443. Ports 8000 and 8443 can be, but they don&#8217;t have to be exposed to the Internet.</p>
<pre class="brush: bash; title: ; notranslate">
firewall-cmd --remove-port=8000/tcp --zone=public --permanent
firewall-cmd --remove-port=8443/tcp --zone=public --permanent
firewall-cmd --reload
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2019/10/aws-centos-create-your-own-radio-station-and-deploy-it-on-alexa-optional/feed/</wfw:commentRss>
			<slash:comments>10</slash:comments>
		
		<enclosure url="https://patrickdearteaga.com/audio/Battleship.ogg" length="1320786" type="audio/ogg" />
<enclosure url="https://patrickdearteaga.com/audio/Chiptronical.ogg" length="4175911" type="audio/ogg" />
<enclosure url="https://patrickdearteaga.com/audio/Friends.ogg" length="1721081" type="audio/ogg" />
<enclosure url="https://radio.iandreev.com/myradio.ogg" length="0" type="audio/ogg" />

			</item>
		<item>
		<title>FreeBSD: icecast &#038; ices &#8211; stream your music on the Internet</title>
		<link>https://blog.andreev.it/2014/09/freebsd-10-icecast-ices/</link>
					<comments>https://blog.andreev.it/2014/09/freebsd-10-icecast-ices/#respond</comments>
		
		<dc:creator><![CDATA[Kliment Andreev]]></dc:creator>
		<pubDate>Sun, 28 Sep 2014 11:54:44 +0000</pubDate>
				<category><![CDATA[FreeBSD]]></category>
		<category><![CDATA[freebsd]]></category>
		<category><![CDATA[icecast]]></category>
		<category><![CDATA[ices]]></category>
		<category><![CDATA[music]]></category>
		<category><![CDATA[streaming]]></category>
		<guid isPermaLink="false">http://blog.iandreev.com/?p=1522</guid>

					<description><![CDATA[In this post I&#8217;ll describe how to install and configure a streaming radio station&#8230;]]></description>
										<content:encoded><![CDATA[<div id="bsf_rt_marker"></div><p>In this post I&#8217;ll describe how to install and configure a streaming radio station with two separate streams. This means, you can have two or multiple radio stations running on the same port. For this, you&#8217;ll need a FreeBSD 10 server and some Ogg Vorbis music files. MP3 is not supported with ices2 client (licensing issues), but it&#8217;s easy to overcome that.</p>
<p>Before going any further, this is what you need to know.</p>
<ul>
<li><a href="http://icecast.org/" target="_blank" rel="noopener noreferrer">icecast</a> is the streaming server</li>
<li><a href="http://icecast.org/ices/" target="_blank" rel="noopener noreferrer">ices</a> is the client that supplies the music files to icecast</li>
<li>you are the listener, not a client</li>
</ul>
<h1>Install icecast</h1>
<p>icecast can be easily installed from the packages or the ports. There is no need for any configurations, so we&#8217;ll do the packages.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install icecast2
</pre>
<p>icecast comes up with a startup script, so we have to enable it on boot. Add the following line to <strong>/etc/rc.conf</strong>.</p>
<pre class="brush: bash; title: ; notranslate">
icecast_enable=&quot;yes&quot;
</pre>
<p>icecast also comes up with a sample config script, so let&#8217;s modify it. The configuration parameters are described in the official <a href="http://icecast.org/docs/" target="_blank" rel="noopener noreferrer">document</a> and this post will highlight only the necessary changes that you have to make. </p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/local/etc/
cp icecast.xml.sample icecast.xml
</pre>
<p>Change the location, the admin email, change the passwords for source-password, relay-password and admin-password under authentication. It&#8217;s a good practice to change the default admin user as well. In addition, change the log directory under logdir and uncomment the user and the group that will run icecast daemon. </p>
<p><a href="https://blog.andreev.it/wp-content/uploads/2014/09/P046-01.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2014/09/P046-01.png" alt="" width="345" height="156" class="aligncenter size-full wp-image-7110" srcset="https://blog.andreev.it/wp-content/uploads/2014/09/P046-01.png 345w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-01-300x136.png 300w" sizes="(max-width: 345px) 100vw, 345px" /></a><br />
This is how my <strong>icecast.xml</strong> looks like. </p>
<pre class="brush: bash; title: ; notranslate">
&lt;icecast&gt;
    &lt;!-- location and admin are two arbitrary strings that are e.g. visible
         on the server info page of the icecast web interface
         (server_version.xsl). --&gt;
    &lt;location&gt;NJ&lt;/location&gt;
    &lt;admin&gt;icemaster@iandreev.com&lt;/admin&gt;

    &lt;limits&gt;
        &lt;clients&gt;10&lt;/clients&gt;
        &lt;sources&gt;2&lt;/sources&gt;
        &lt;threadpool&gt;5&lt;/threadpool&gt;
        &lt;queue-size&gt;524288&lt;/queue-size&gt;
        &lt;client-timeout&gt;30&lt;/client-timeout&gt;
        &lt;header-timeout&gt;15&lt;/header-timeout&gt;
        &lt;source-timeout&gt;10&lt;/source-timeout&gt;
        &lt;!-- If enabled, this will provide a burst of data when a client 
             first connects, thereby significantly reducing the startup 
             time for listeners that do substantial buffering. However,
             it also significantly increases latency between the source
             client and listening client.  For low-latency setups, you
             might want to disable this. --&gt;
        &lt;burst-on-connect&gt;1&lt;/burst-on-connect&gt;
        &lt;!-- same as burst-on-connect, but this allows for being more
             specific on how much to burst. Most people won't need to
             change from the default 64k. Applies to all mountpoints  --&gt;
        &lt;burst-size&gt;65535&lt;/burst-size&gt;
    &lt;/limits&gt;

    &lt;authentication&gt;
        &lt;!-- Sources log in with username 'source' --&gt;
        &lt;source-password&gt;password1&lt;/source-password&gt;
        &lt;!-- Relays log in username 'relay' --&gt;
        &lt;relay-password&gt;password2&lt;/relay-password&gt;

        &lt;!-- Admin logs in with the username given below --&gt;
        &lt;admin-user&gt;username&lt;/admin-user&gt;
        &lt;admin-password&gt;password3&lt;/admin-password&gt;
    &lt;/authentication&gt;

    &lt;!-- 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
    &lt;shoutcast-mount&gt;/live.nsv&lt;/shoutcast-mount&gt;
    --&gt;

    &lt;!-- Uncomment this if you want directory listings --&gt;
    &lt;!--
    &lt;directory&gt;
        &lt;yp-url-timeout&gt;15&lt;/yp-url-timeout&gt;
        &lt;yp-url&gt;http://dir.xiph.org/cgi-bin/yp-cgi&lt;/yp-url&gt;
    &lt;/directory&gt;
     --&gt;

    &lt;!-- This is the hostname other people will use to connect to your server.
    It affects mainly the urls generated by Icecast for playlists and yp
    listings. --&gt;
    &lt;hostname&gt;localhost&lt;/hostname&gt;

    &lt;!-- You may have multiple &lt;listener&gt; elements --&gt;
    &lt;listen-socket&gt;
        &lt;port&gt;8000&lt;/port&gt;
        &lt;!-- &lt;bind-address&gt;127.0.0.1&lt;/bind-address&gt; --&gt;
        &lt;!-- &lt;shoutcast-mount&gt;/stream&lt;/shoutcast-mount&gt; --&gt;
    &lt;/listen-socket&gt;
    &lt;!--
    &lt;listen-socket&gt;
        &lt;port&gt;8001&lt;/port&gt;
    &lt;/listen-socket&gt;
    --&gt;

    &lt;!--&lt;master-server&gt;127.0.0.1&lt;/master-server&gt;--&gt;
    &lt;!--&lt;master-server-port&gt;8001&lt;/master-server-port&gt;--&gt;
    &lt;!--&lt;master-update-interval&gt;120&lt;/master-update-interval&gt;--&gt;
    &lt;!--&lt;master-password&gt;hackme&lt;/master-password&gt;--&gt;

    &lt;!-- setting this makes all relays on-demand unless overridden, this is
         useful for master relays which do not have &lt;relay&gt; definitions here.
         The default is 0 --&gt;
    &lt;!--&lt;relays-on-demand&gt;1&lt;/relays-on-demand&gt;--&gt;

    &lt;!--
    &lt;relay&gt;
        &lt;server&gt;127.0.0.1&lt;/server&gt;
        &lt;port&gt;8001&lt;/port&gt;
        &lt;mount&gt;/example.ogg&lt;/mount&gt;
        &lt;local-mount&gt;/different.ogg&lt;/local-mount&gt;
        &lt;on-demand&gt;0&lt;/on-demand&gt;

        &lt;relay-shoutcast-metadata&gt;0&lt;/relay-shoutcast-metadata&gt;
    &lt;/relay&gt;
    --&gt;

    &lt;!-- Only define a &lt;mount&gt; section if you want to use advanced options,
         like alternative usernames or passwords
    &lt;mount&gt;
        &lt;mount-name&gt;/example-complex.ogg&lt;/mount-name&gt;

        &lt;username&gt;othersource&lt;/username&gt;
        &lt;password&gt;hackmemore&lt;/password&gt;

        &lt;max-listeners&gt;1&lt;/max-listeners&gt;
        &lt;dump-file&gt;/tmp/dump-example1.ogg&lt;/dump-file&gt;
        &lt;burst-size&gt;65536&lt;/burst-size&gt;
        &lt;fallback-mount&gt;/example2.ogg&lt;/fallback-mount&gt;
        &lt;fallback-override&gt;1&lt;/fallback-override&gt;
        &lt;fallback-when-full&gt;1&lt;/fallback-when-full&gt;
        &lt;intro&gt;/example_intro.ogg&lt;/intro&gt;
        &lt;hidden&gt;1&lt;/hidden&gt;
        &lt;no-yp&gt;1&lt;/no-yp&gt;
        &lt;authentication type=&quot;htpasswd&quot;&gt;
                &lt;option name=&quot;filename&quot; value=&quot;myauth&quot;/&gt;
                &lt;option name=&quot;allow_duplicate_users&quot; value=&quot;0&quot;/&gt;
        &lt;/authentication&gt;
        &lt;on-connect&gt;/home/icecast/bin/stream-start&lt;/on-connect&gt;
        &lt;on-disconnect&gt;/home/icecast/bin/stream-stop&lt;/on-disconnect&gt;
    &lt;/mount&gt;

    &lt;mount&gt;
        &lt;mount-name&gt;/auth_example.ogg&lt;/mount-name&gt;
        &lt;authentication type=&quot;url&quot;&gt;
            &lt;option name=&quot;mount_add&quot;       value=&quot;http://myauthserver.net/notify_mount.php&quot;/&gt;
            &lt;option name=&quot;mount_remove&quot;    value=&quot;http://myauthserver.net/notify_mount.php&quot;/&gt;
            &lt;option name=&quot;listener_add&quot;    value=&quot;http://myauthserver.net/notify_listener.php&quot;/&gt;
            &lt;option name=&quot;listener_remove&quot; value=&quot;http://myauthserver.net/notify_listener.php&quot;/&gt;
            &lt;option name=&quot;headers&quot;         value=&quot;x-pragma,x-token&quot;/&gt;
            &lt;option name=&quot;header_prefix&quot;   value=&quot;ClientHeader.&quot;/&gt;
        &lt;/authentication&gt;
    &lt;/mount&gt;

    --&gt;

    &lt;fileserve&gt;1&lt;/fileserve&gt;

    &lt;paths&gt;
		&lt;!-- basedir is only used if chroot is enabled --&gt;
        &lt;basedir&gt;/usr/local/share/icecast&lt;/basedir&gt;

        &lt;!-- Note that if &lt;chroot&gt; is turned on below, these paths must both
             be relative to the new root, not the original root --&gt;
        &lt;logdir&gt;/var/log/icecast&lt;/logdir&gt;
        &lt;webroot&gt;/usr/local/share/icecast/web&lt;/webroot&gt;
        &lt;adminroot&gt;/usr/local/share/icecast/admin&lt;/adminroot&gt;
        &lt;!-- &lt;pidfile&gt;/usr/local/share/icecast/icecast.pid&lt;/pidfile&gt; --&gt;

        &lt;!-- Aliases: treat requests for 'source' path as being for 'dest' path
             May be made specific to a port or bound address using the &quot;port&quot;
             and &quot;bind-address&quot; attributes.
          --&gt;
        &lt;!--
        &lt;alias source=&quot;/foo&quot; destination=&quot;/bar&quot;/&gt;
          --&gt;
        &lt;!-- Aliases: can also be used for simple redirections as well,
             this example will redirect all requests for http://server:port/ to
             the status page
          --&gt;
        &lt;alias source=&quot;/&quot; destination=&quot;/status.xsl&quot;/&gt;
    &lt;/paths&gt;

    &lt;logging&gt;
        &lt;accesslog&gt;access.log&lt;/accesslog&gt;
        &lt;errorlog&gt;error.log&lt;/errorlog&gt;
        &lt;!-- &lt;playlistlog&gt;playlist.log&lt;/playlistlog&gt; --&gt;
      	&lt;loglevel&gt;3&lt;/loglevel&gt; &lt;!-- 4 Debug, 3 Info, 2 Warn, 1 Error --&gt;
      	&lt;logsize&gt;10000&lt;/logsize&gt; &lt;!-- Max size of a logfile --&gt;
        &lt;!-- If logarchive is enabled (1), then when logsize is reached
             the logfile will be moved to &#x5B;error|access|playlist].log.DATESTAMP,
             otherwise it will be moved to &#x5B;error|access|playlist].log.old.
             Default is non-archive mode (i.e. overwrite)
        --&gt;
        &lt;!-- &lt;logarchive&gt;1&lt;/logarchive&gt; --&gt;
    &lt;/logging&gt;

    &lt;security&gt;
        &lt;chroot&gt;0&lt;/chroot&gt;
        &lt;changeowner&gt;
            &lt;user&gt;nobody&lt;/user&gt;
            &lt;group&gt;nogroup&lt;/group&gt;
        &lt;/changeowner&gt;
    &lt;/security&gt;
&lt;/icecast&gt;
</pre>
<p>Create the log directory and change the ownership.</p>
<pre class="brush: bash; title: ; notranslate">
mkdir /var/log/icecast
chown -R nobody:nogroup /var/log/icecast
</pre>
<p>Start the icecast and check the log file.</p>
<pre class="brush: bash; title: ; notranslate">
service icecast2 start
tail /var/log/icecast/error.log
</pre>
<p>If everything is OK you&#8217;ll see something like this.</p>
<pre class="brush: plain; title: ; notranslate">
&#x5B;2014-09-13  23:34:13] INFO main/main Icecast 2.4.0 server started
&#x5B;2014-09-13  23:34:13] INFO connection/get_ssl_certificate No SSL capability on any configured ports
&#x5B;2014-09-13  23:34:13] INFO yp/yp_update_thread YP update thread started
</pre>
<p>Go to <strong>http://yourserver.com:8000</strong> and you should see this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2014/09/P046-02.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2014/09/P046-02.png" alt="" width="916" height="248" class="aligncenter size-full wp-image-7111" srcset="https://blog.andreev.it/wp-content/uploads/2014/09/P046-02.png 916w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-02-300x81.png 300w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-02-768x208.png 768w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-02-585x158.png 585w" sizes="(max-width: 916px) 100vw, 916px" /></a><br />
Then go to <strong>http://yourserver.com:8000/admin/stats.xml</strong> and you should see this.<br />
<a href="https://blog.andreev.it/wp-content/uploads/2014/09/P046-03.png"><img loading="lazy" decoding="async" src="https://blog.andreev.it/wp-content/uploads/2014/09/P046-03.png" alt="" width="765" height="494" class="aligncenter size-full wp-image-7112" srcset="https://blog.andreev.it/wp-content/uploads/2014/09/P046-03.png 765w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-03-300x194.png 300w, https://blog.andreev.it/wp-content/uploads/2014/09/P046-03-585x378.png 585w" sizes="(max-width: 765px) 100vw, 765px" /></a></p>
<h1>Install ices</h1>
<p>We&#8217;ll install ices from the packages and create a separate user and group called <strong>radio</strong> that will run the ices client. Ices client doesn&#8217;t come up with a startup script, so we&#8217;ll create one for each stream. In case you reboot, the streaming server and the clients will start automatically. We&#8217;ll also create a log directory. </p>
<pre class="brush: bash; title: ; notranslate">
pkg install ices
pw groupadd radio &amp;&amp; pw useradd radio -g radio –m
mkdir /var/log/ices
chown –R radio:radio /var/log/ices
</pre>
<p>Switch to the <strong>radio</strong> user and create the folders for the configuration files and the two radio stations that you want to run. If you want more streams, just follow the pattern described bellow. In my case, I&#8217;ll have two radio stations, trance and liquid.</p>
<pre class="brush: bash; title: ; notranslate">
su - radio
mkdir conf liquid trance
cd ~radio/conf
cp /usr/local/share/ices/ices-playlist.xml liquid-playlist.xml
</pre>
<p>Let&#8217;s configure the first radio station and you&#8217;ll see how easy is to configure the the second one. </p>
<ul>
<li>Edit the configuration file for the first stream (<strong>liquid-playlist.xml</strong>) and change the following values: background, logpath, logfile and pidfile.</li>
<li>Under <strong>stream</strong> section, change the name, genre and description.</li>
<li>Under the <strong>input</strong> section, change the <strong>param name=&#8221;file&#8221;</strong> parameter. This value should point to a file that contains each Ogg file in a separate line.</li>
<li>Under the <strong>instance</strong> section, change the password and the mount. The password supplied here must match the same <strong>source-password</strong> that was used to configure icecast (see line 31 in my example of icecast config). The mount parameter is how you are going to access the stream (e.g. <strong>http://yourserver.com:8000/station_name.ogg</strong>). Always end it up with <strong>.ogg</strong>.</li>
<li>I also change the <strong>nominal-bitrate</strong> to 128000, which means, ices will encode the streams as 128kbps streams. </li>
</ul>
<p>Make sure you have enough bandwidth to support this. Use the following <a href="http://servers.internet-radio.com/tools/bandwidth/" target="_blank" rel="noopener noreferrer">link</a> to determine your needs. </p>
<p>Finally, here is my <strong>liquid-playlist.xml</strong> file. </p>
<pre class="brush: bash; title: ; notranslate">
&lt;?xml version=&quot;1.0&quot;?&gt;
&lt;ices&gt;
    &lt;!-- run in background --&gt;
    &lt;background&gt;1&lt;/background&gt;
    &lt;!-- where logs, etc go. --&gt;
    &lt;logpath&gt;/var/log/ices&lt;/logpath&gt;
    &lt;logfile&gt;liquid.log&lt;/logfile&gt;
    &lt;!-- 1=error,2=warn,3=info,4=debug --&gt;
    &lt;loglevel&gt;4&lt;/loglevel&gt;
    &lt;!-- set this to 1 to log to the console instead of to the file above --&gt;
    &lt;consolelog&gt;0&lt;/consolelog&gt;

    &lt;!-- optional filename to write process id to --&gt;
    &lt;pidfile&gt;/home/radio/liquid.pid&lt;/pidfile&gt;

    &lt;stream&gt;
        &lt;!-- metadata used for stream listing (not currently used) --&gt;
        &lt;metadata&gt;
            &lt;name&gt;Liquid radio&lt;/name&gt;
            &lt;genre&gt;Liquid D'n'B genre&lt;/genre&gt;
            &lt;description&gt;Commercial Free Liquid D'n'B radio stream&lt;/description&gt;
        &lt;/metadata&gt;

        &lt;!-- input module

            The module used here is the playlist module - it has 
            'submodules' for different types of playlist. There are
            two currently implemented, 'basic', which is a simple
            file-based playlist, and 'script' which invokes a command
            to returns a filename to start playing. --&gt;

        &lt;input&gt;
            &lt;module&gt;playlist&lt;/module&gt;
            &lt;param name=&quot;type&quot;&gt;basic&lt;/param&gt;
            &lt;param name=&quot;file&quot;&gt;/home/radio/conf/liquid-playlist.txt&lt;/param&gt;
            &lt;!-- random play --&gt;
            &lt;param name=&quot;random&quot;&gt;0&lt;/param&gt;
            &lt;!-- if the playlist get updated that start at the beginning --&gt;
            &lt;param name=&quot;restart-after-reread&quot;&gt;0&lt;/param&gt;
            &lt;!-- if set to 1 , plays once through, then exits. --&gt;
            &lt;param name=&quot;once&quot;&gt;0&lt;/param&gt;
        &lt;/input&gt;

		&lt;!-- Stream instance
            You may have one or more instances here. This allows you to 
            send the same input data to one or more servers (or to different
            mountpoints on the same server). Each of them can have different
            parameters. This is primarily useful for a) relaying to multiple
            independent servers, and b) encoding/reencoding to multiple
            bitrates.
            If one instance fails (for example, the associated server goes
            down, etc), the others will continue to function correctly.
            This example defines two instances as two mountpoints on the
            same server.  --&gt;
        &lt;instance&gt;
            &lt;!-- Server details:
                You define hostname and port for the server here, along with
                the source password and mountpoint.  --&gt;
            &lt;hostname&gt;localhost&lt;/hostname&gt;
            &lt;port&gt;8000&lt;/port&gt;
            &lt;password&gt;password1&lt;/password&gt;
            &lt;mount&gt;/liquid.ogg&lt;/mount&gt;

            &lt;!-- Reconnect parameters:
                When something goes wrong (e.g. the server crashes, or the
                network drops) and ices disconnects from the server, these
                control how often it tries to reconnect, and how many times
                it tries to reconnect. Delay is in seconds.
                If you set reconnectattempts to -1, it will continue 
                indefinately. Suggest setting reconnectdelay to a large value
                if you do this.
            --&gt;
            &lt;reconnectdelay&gt;2&lt;/reconnectdelay&gt;
            &lt;reconnectattempts&gt;5&lt;/reconnectattempts&gt; 

            &lt;!-- maxqueuelength:
                This describes how long the internal data queues may be. This
                basically lets you control how much data gets buffered before
                ices decides it can't send to the server fast enough, and 
                either shuts down or flushes the queue (dropping the data)
                and continues. 
                For advanced users only.
            --&gt;
            &lt;maxqueuelength&gt;80&lt;/maxqueuelength&gt;

            &lt;!-- Live encoding/reencoding:
                Currrently, the parameters given here for encoding MUST
                match the input data for channels and sample rate. That 
                restriction will be relaxed in the future.
                Remove this section if you don't want your files getting reencoded.
            --&gt;
            &lt;encode&gt;  
                &lt;nominal-bitrate&gt;128000&lt;/nominal-bitrate&gt; &lt;!-- bps. e.g. 64000 for 64 kbps --&gt;
                &lt;samplerate&gt;44100&lt;/samplerate&gt;
                &lt;channels&gt;2&lt;/channels&gt;
            &lt;/encode&gt;
        &lt;/instance&gt;

	&lt;/stream&gt;
&lt;/ices&gt;
</pre>
<p>At this point, we need to put some Ogg files that we want to stream. In my case, I&#8217;ve put them under /<strong>home/radio/liquid</strong> for the first stream. Now, go to:</p>
<pre class="brush: bash; title: ; notranslate">
cd ~radio/conf
ls -d /home/radio/liquid/*.ogg &gt; liquid-playlist.txt
cat liquid-playlist.txt
</pre>
<p>This will generate the playlist for you from all Ogg files. If you have your files separated in sub directories, then do:</p>
<pre class="brush: bash; title: ; notranslate">
cd ~radio/genre
find /home/radio/genre -name &quot;*.ogg&quot; -print &gt; /home/radio/conf/genre-playlist.txt
</pre>
<p>At this point, start the station with <strong>ices liquid-playlist.xml</strong>. If everything is OK, you shouldn&#8217;t see anything. Ices will start in the background. Do a simple check:</p>
<pre class="brush: bash; title: ; notranslate">
ps -waux | grep ices
tail /var/log/ices/liquid.log
</pre>
<p>Check your radio with <strong>http://yourserver.com:8000/liquid.ogg</strong>. Now that you have the first radio ready, it&#8217;s very easy to configure the 2nd one. First, let&#8217;s create the configuration file as a copy from the first station.</p>
<pre class="brush: bash; title: ; notranslate">
cd ~radio/conf
cp liquid-playlist.xml trance-playlist.xml
</pre>
<p>I use <strong>vi</strong> to replace everything in <strong>trance-playlist.xml</strong> that says liquid as trance. Use <strong>:%s/liquid/trance/g</strong>.</p>
<p>Put some Ogg file for the 2nd station under <strong>~radio/trance</strong> and create the playlist text file. </p>
<pre class="brush: bash; title: ; notranslate">
cd ~radio/conf
ls -d /home/radio/trance/*.ogg &gt; trance-playlist.txt
cat trance-playlist.txt
</pre>
<p>Start the station with <strong>ices trance-playlist.xml</strong> and test it with <strong>http://yourserver.com:8000/trance.ogg</strong>.</p>
<h1>Configure ices startup</h1>
<p>Once you make sure that everything works fine, let&#8217;s create the ices startup files in case the server reboots. </p>
<p>Log as root and do:</p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/local/etc/rc.d
</pre>
<p>Create the first startup script called <strong>ices_liquid</strong> and add everything from here.</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
#
# PROVIDE: ices_liquid
# REQUIRE: DAEMON icecast2
# BEFORE:  LOGIN
# KEYWORD: shutdown

. /etc/rc.subr

name=&quot;ices_liquid&quot;
rcvar=ices_liquid_enable

command=&quot;/usr/local/bin/ices&quot;
extra_commands=&quot;reload&quot;
pidfile=&quot;/home/radio/liquid.pid&quot;
sig_reload=&quot;USR1&quot;

load_rc_config &quot;$name&quot;
: ${ices_liquid_enable=&quot;NO&quot;}
: ${ices_liquid_config=&quot;/home/radio/conf/liquid-playlist.xml&quot;}
: ${ices_liquid_flags=&quot;${ices_liquid_config}&quot;}
: ${ices_liquid_user=&quot;radio&quot;}
required_files=&quot;${ices_liquid_config}&quot;

run_rc_command &quot;$1&quot;

</pre>
<p>Save it and do <strong>chmod 555 ices_liquid</strong> to make sure it&#8217;s executable on start. Add <strong>ices_liquid_enable=&#8221;YES&#8221;</strong> in <strong>/etc/rc.conf</strong>. Follow the same pattern for the second startup script. Copy ices_liquid as ices_trance and replace every occurrence of liquid as trance. Then add <strong>ices_trance_enable=&#8221;YES&#8221;</strong> in <strong>/etc/rc.conf</strong>. Now you can control the streams with the standard FreeBSD daemon command <strong>service</strong>. E.g.</p>
<pre class="brush: bash; title: ; notranslate">
service ices_liquid start
service ices_liquid stop
service ices_liquid status
</pre>
<p>OK. At this point you have a streaming station(s) but they all run on port 8000. As we stated above, you can access a station by accessing this URL: <strong>http://genre.domain.com:8000/genre.ogg</strong>, speaking in general terms. Read further to see how can you bypass this restriction in order to access your radio behind a firewall.</p>
<h1>Firewall</h1>
<p>icecast runs on port 8000 and in many corporate environments only ports 80 and 443 are allowed inbound through various proxies. I tried to run icecast on port 80, but icecast refused to run. Mind that you can&#8217;t run icecast using a non-root account on port 80. Actually, you can. apache runs on port 80 as www user, but it&#8217;s parent process runs as root and then forks the processes. But most importantly, if you somehow make icecast to run as root, you can&#8217;t run apache on that port. The easiest way is to use <strong>mod_proxy</strong>. This module will do the heavy lifting for you. It will redirect port 8000 to port 80. This is how we are going to accomplish that using two virtual hosts in Apache.<br />
First, create two DNS aliases, e.g. <strong>genre.domain.com</strong> and <strong>genrefw.domain.com</strong>. They should point to the same IP of your icecast server. In Apache, make sure you have <strong>mod_proxy</strong> and <strong>mod_proxy_html</strong> modules built in and loaded in <strong>httpd.conf</strong>. See my other <a href="https://blog.andreev.it/?p=1309" target="_blank" rel="noopener noreferrer">post</a> on how to run virtual hosts under Apache. </p>
<pre class="brush: bash; title: ; notranslate">
LoadModule proxy_module libexec/apache24/mod_proxy.so
LoadModule proxy_http_module libexec/apache24/mod_proxy_http.so
LoadModule cgi_module libexec/apache24/mod_cgi.so
</pre>
<p>or if you have the prefork/worker config file, you might also see this.</p>
<pre class="brush: plain; title: ; notranslate">
&lt;IfModule mpm_prefork_module&gt;
        LoadModule cgi_module libexec/apache24/mod_cgi.so
&lt;/IfModule&gt;
</pre>
<p>The virtual hosts directives in <strong>httpd-vhosts.conf</strong> should look like this.</p>
<pre class="brush: bash; title: ; notranslate">
&lt;VirtualHost *:80&gt;
    ServerAdmin admin@domain.com
    DocumentRoot &quot;/usr/local/www/genre.domain.com&quot;
    ServerName genre.domain.com
    ErrorLog &quot;/var/log/genre.domain.com-error_log&quot;
    CustomLog &quot;/var/log/genre.domain.com-access_log&quot; common
    &lt;Directory &quot;/usr/local/www/genre.domain.com&quot;&gt;
        Options All
        AllowOverride All
        Require all granted
   &lt;/Directory&gt;
   ScriptAlias /cgi-bin/ &quot;/usr/local/www/genre.domain.com/cgi-bin/&quot;
   &lt;Directory &quot;/usr/local/www/genre.domain.com/cgi-bin&quot;&gt;
        Options +ExecCGI
        Order allow,deny
        Allow from all
        AddHandler cgi-script .cgi
   &lt;/Directory&gt;
&lt;/VirtualHost&gt;
&lt;VirtualHost *:80&gt;
    ServerAdmin admin@domain.com
    DocumentRoot &quot;/usr/local/www/genrefw.domain.com&quot;
    ServerName genrefw.domain.com
    ErrorLog &quot;/var/log/genrefw.domain.com-error_log&quot;
    CustomLog &quot;/var/log/genrefw.domain.com-access_log&quot; common
    ProxyRequests Off
    ProxyPass / http://genre.domain.com:8000/genre.ogg
    ProxyPassReverse / http://genre.domain.com:8000/genre.ogg
    &lt;Directory &quot;/usr/local/www/genrefw.domain.com&quot;&gt;
        Options All
        AllowOverride All
        Require all granted
    &lt;/Directory&gt;
&lt;/VirtualHost&gt;
</pre>
<p>Now, you have to create two virtual web sites, <strong>genre.domain.com</strong> and <strong>genrefw.domain.com</strong>. The <strong>genrefw.domain.com</strong> site will be an empty site. Read further and see how I built two web sites. You can make it much simpler or even more complex. I also enabled cgi-bin in order to run some scripts. The step below is optional. If you just want to bypass the firewall, create the two virtual hosts as described above and for regular users use <strong>http://genre.domain.com:8000/genre.ogg</strong> and for the users behind a firewall use <strong>http://genrefw.domain.com/genre.ogg</strong>.</p>
<h1>Optional</h1>
<p>I&#8217;ll create a web page that embeds the player and shows some info. There is a HTML5 menu and one of the options will tell you what track is currently playing. That&#8217;s why we needed cgi-bin. Here are my HTML and CSS files. Make sure you have 10 background images in the same directory, from background0.jpg to background9.jpg. They will rotate every 90 seconds. It looks cool! :) First, let&#8217;s create the cgi-bin folder under the virtual host destination.</p>
<pre class="brush: bash; title: ; notranslate">
cd /usr/local/www/genre.domain.com
mkdir cgi-bin
</pre>
<p>Then create a file called <strong>info.cgi</strong> that contains the following:</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
echo &quot;Content-type: text/html&quot;
echo &quot;&quot;

echo '&lt;html&gt;'
echo '&lt;head&gt;'
echo '&lt;meta http-equiv=&quot;Content-Type&quot; content=&quot;text/html; charset=UTF-8&quot;&gt;'
echo '&lt;title&gt;Currently Playing &lt;/title&gt;'
echo '&lt;/head&gt;'
echo '&lt;body&gt;'
grep &quot;INFO playlist&quot; /var/log/ices/genre.log | tail -n 1 | sed 's/ INFO playlist-builtin\/playlist_read//' | sed 's/&quot;//g' | sed 's/\/home\/radio\/genre\///'
echo '&lt;/body&gt;'
echo '&lt;/html&gt;'
exit 0
</pre>
<p>Then change permissions.</p>
<pre class="brush: plain; title: ; notranslate">
chmod 755 info.cgi
</pre>
<p>What this script does? It creates a simple HTML file from the output of the grep command. It just looks for the last appearance of the current playing track in the ices log. </p>
<p>Here is the HTML file.</p>
<pre class="brush: xml; title: ; notranslate">
&lt;html&gt;
    &lt;head&gt;
        &lt;title&gt;Genre Radio&lt;/title&gt;
            &lt;link rel=&quot;stylesheet&quot; href=&quot;styles.css&quot; /&gt;
	    &lt;script&gt;
                function run(interval, frames) {
                     var int = 0;
    		     function func() {
        		document.body.id = &quot;bg&quot;+int;
        		int++;
        		if(int == frames) { int = 0; }
    		     }
   		     var swap = window.setInterval(func, interval);
	        }
		run(90000, 10); //milliseconds, frames
	&lt;/script&gt;
    &lt;/head&gt;
&lt;body id=&quot;bg&quot;&gt;
&lt;!-- http://red-team-design.com/css3-dropdown-menu/ --&gt;
&lt;BR&gt;&amp;nbsp;&lt;BR&gt; 
&lt;ul id=&quot;menu&quot;&gt;
    &lt;li&gt;&lt;a href=&quot;/&quot; onclick=&quot;window.open('http://genre.domain.com/cgi-bin/info.cgi', 'newwindow', 'width=300, height=100'); return false;&quot;&gt;INFO&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;
        &lt;a href=&quot;#&quot;&gt;Radio&lt;/a&gt;
        &lt;ul&gt;
            &lt;li&gt;&lt;a href=&quot;#&quot;&gt;Classical&lt;/a&gt;&lt;/li&gt;
            &lt;li&gt;&lt;a href=&quot;http://genre2.domain.com/&quot; target=&quot;_blank&quot;&gt;Other Genre&lt;/a&gt;&lt;/li&gt;
        &lt;/ul&gt;
    &lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.andreev.it/&quot; target=&quot;_blank&quot;&gt;About&lt;/a&gt;&lt;/li&gt;
    &lt;li&gt;&lt;a href=&quot;https://www.linkedin.com/in/klimenta/&quot; target=&quot;_blank&quot;&gt;Contact&lt;/a&gt;&lt;/li&gt;
&lt;/ul&gt;
    &lt;footer&gt;
        &lt;audio controls&gt;
             &lt;source src=&quot;http://genre.domain.com:8000/genre.ogg&quot; type=&quot;audio/ogg&quot;&gt;
             Your browser does not support the audio element.
        &lt;/audio&gt;
&lt;a class=&quot;tzine1&quot; href=&quot;/&quot; onclick=&quot;window.open('http://genrefw.iandreev.com', 'newwindow', 'width=300, height=100'); return false;&quot;&gt;No sound? Click &lt;i&gt;&lt;b&gt;here!&lt;/b&gt;&lt;/i&gt;&lt;/a&gt;
&lt;a class=&quot;tzine&quot; href=&quot;https://blog.andreev.it/&quot;&gt;Commercial Free Genre Radio: &lt;i&gt;Kliment Andreev&lt;b&gt; 2015&lt;/b&gt;&lt;/i&gt; (c)&lt;/a&gt;
    &lt;/footer&gt;
&lt;/body&gt;
&lt;/html&gt;
</pre>
<p>And the CSS file.</p>
<pre class="brush: css; title: ; notranslate">
*{
    margin: 0;
    padding: 0;
}

body#bg0 {
  background: url('background0.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg1 {
  background: url('background1.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg2 {
  background: url('background2.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg3 {
  background: url('background.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg4 {
  background: url('background4.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg5 {
  background: url('background5.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg6 {
  background: url('background6.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg7 {
  background: url('background7.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg8 {
  background: url('background8.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}
body#bg9 {
  background: url('background9.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;
}

a, a:visited {
        outline:none;
        color:#1c4f64;
}

a:hover{
        text-decoration:none;
}

section, footer, header{
        display: block;
}


footer{
        background-color: #111111;
        bottom: 0;
        box-shadow: 0 -1px 2px rgba(0,0,0,0.4);
        height: 45px;
        left: 0;
        position: fixed;
        width: 100%;
        z-index: 100000;
}

footer h2{
        color: #EEEEEE;
        font-size: 14px;
        font-weight: normal;
        left: 50%;
        margin-left: -400px;
        padding: 13px 0 0;
        position: absolute;
        width: 540px;
}
footer h2 i{
        font-style:normal;
        color:#888;
}

footer a.tzine,a.tzine:visited{
        color: #999999;
        font-size: 12px;
        left: 50%;
        margin: 16px 0 0 110px;
        position: absolute;
        text-decoration: none;
        top: 0;
}

footer a.tzine1,a.tzine1:visited{
        color: #999999;
        font-size: 12px;
        left: -5%;
        margin: 16px 0 0 110px;
        position: absolute;
        text-decoration: none;
        top: 0;
}

footer a i{
        color:#ccc;
        font-style: normal;
}

footer a i b{
        color:#c92020;
        font-weight: normal;
}


body{
        /* Workaround for some mobile browsers */
  background: url('background9.jpg') no-repeat 50% 50% fixed;
  background-size: cover;
  -moz-background-size: cover;
  -o-background-size: cover;
  -webkit-background-size: cover;

        font:14px/1.3 'Segoe UI',Arial, sans-serif;
}

/* Main */
/* Original width e 100% a ne 30%&quot;*/
#menu {
    width: 30%;
    margin: 0;
    padding: 10px 0 0 0;
    list-style: none;  
    background-color: #111;
    background-image: linear-gradient(#444, #111);
    border-radius: 50px;
    box-shadow: 0 2px 1px #9c9c9c;
}

#menu li {
    float: left;
    padding: 0 0 10px 0;
    position: relative;
}

#menu a {
    float: left;
    height: 25px;
    padding: 0 25px;
    color: #999;
    text-transform: uppercase;
    font: bold 12px/25px Arial, Helvetica;
    text-decoration: none;
    text-shadow: 0 1px 0 #000;
}

#menu li:hover &gt; a {
    color: #fafafa;
}

*html #menu li a:hover { /* IE6 */
    color: #fafafa;
}

#menu li:hover &gt; ul {
    display: block;
}

/* Sub-menu */
#menu ul {
    list-style: none;
    margin: 0;
    padding: 0;    
    display: none;
    position: absolute;
    top: 35px;
    left: 0;
    z-index: 99999;    
    background-color: #444;   
    background-image: linear-gradient(#444, #111);    
    -moz-border-radius: 5px;
    border-radius: 5px;
}

#menu ul li {
    float: none;
    margin: 0;
    padding: 0;
    display: block;  
    box-shadow: 0 1px 0 #111111, 
                0 2px 0 #777777;
}

#menu ul li:last-child { 
    box-shadow: none;    
}

#menu ul a {    
    padding: 10px;
    height: auto;
    line-height: 1;
    display: block;
    white-space: nowrap;
    float: none;
    text-transform: none;
}

*html #menu ul a { /* IE6 */   
    height: 10px;
    width: 150px;
}

*:first-child+html #menu ul a { /* IE7 */    
    height: 10px;
    width: 150px;
}

#menu ul a:hover {
    background-color: #0186ba;
    background-image: linear-gradient(#04acec, #0186ba);
}

#menu ul li:first-child a {
    border-radius: 5px 5px 0 0;
}

#menu ul li:first-child a:after {
    content: '';
    position: absolute;
    left: 30px;
    top: -8px;
    width: 0;
    height: 0;
    border-left: 5px solid transparent;
    border-right: 5px solid transparent;
    border-bottom: 8px solid #444;
}

#menu ul li:first-child a:hover:after {
    border-bottom-color: #04acec; 
}

#menu ul li:last-child a {
    border-radius: 0 0 5px 5px;
}

/* Clear floated elements */
#menu:after {
    visibility: hidden;
    display: block;
    font-size: 0;
    content: &quot; &quot;;
    clear: both;
    height: 0;
}

* html #menu             { zoom: 1; } /* IE6 */
*:first-child+html #menu { zoom: 1; } /* IE7 */

</pre>
<h1>Convert mp3 to ogg</h1>
<p>You can use the <strong>sox</strong> utility that comes in the ports.</p>
<pre class="brush: bash; title: ; notranslate">
pkg install sox
</pre>
<p>Check the man page for detailed syntax, but I use simple conversion.</p>
<pre class="brush: bash; title: ; notranslate">
sox filename.mp3 filename.ogg
</pre>
<p>You can check the properties of any audio file with:</p>
<pre class="brush: bash; title: ; notranslate">
soxi filename
</pre>
<p>For batch conversion, create this script called <strong>mp32ogg.sh</strong> and execute it against all files in the folder. It will convert all mp3 files to ogg. Add sox parameters if needed.</p>
<pre class="brush: bash; title: ; notranslate">
#!/bin/sh
for i in *.mp3; do
        sox ./&quot;$i&quot; ./&quot;${i%.mp3}.ogg&quot;
done
</pre>
]]></content:encoded>
					
					<wfw:commentRss>https://blog.andreev.it/2014/09/freebsd-10-icecast-ices/feed/</wfw:commentRss>
			<slash:comments>0</slash:comments>
		
		<enclosure url="http://genrefw.domain.com/genre.ogg" length="767" type="audio/ogg" />

			</item>
	</channel>
</rss>
