Load Balancing/Clustering Your Shoutcast Servers Once the Shoutcast Directory Goes Down

Some folks are worried what will happen to their cluster of servers when the Shoutcast directory finally shuts down. Not to worry, it’s easy to replace this functionality!

Create the file below. You can name it whatever you’d like. Add in your server info (creating more entries if necessary). This will be what ALL listeners should be pointed to to tune in. For example if I named it tunein.php, the link I would give out would be http://domain.com/tunein.php.

This script will send the listener to the first server in the list that isn’t “full.” One of the options I’ve given you is to define what full means. The minimum_free value allows you to specify how many free slots should remain on the server before the script skips to the next server.

<?php
 
//don't want folks caching this...
header("Cache-Control: no-cache, must-revalidate");
 
//include the shoutcast class
require_once('shoutcast.class.php');
 
//list of servers
$servers = array(
    array(
        'host' => 'http://192.168.1.1',
        'port' => '8000',
        'user' => 'admin',
        'pass' => 'hackme',
        'minimum_free' => 10 //minimum number of free slots
    ),
    array(
        'host' => 'http://192.168.1.1',
        'port' => '8002',
        'user' => 'admin',
        'pass' => 'hackme',
        'minimum_free' => 5 //minimum number of free slots
    ),
);
 
foreach ($servers as $i => $s) {
    $shoutcast = new ShoutCast();
    $shoutcast->host = $s['host'];
    $shoutcast->port = $s['port'];
    $shoutcast->passwd = $s['pass'];
 
    if ($shoutcast->openstats()) {
        if ($shoutcast->GetStreamStatus()) {
            $current = $shoutcast->GetCurrentListenersCount();
            $max = $shoutcast->GetMaxListenersCount() - $s['minimum_free'];
 
            if ($current < $max) {
                header("location:http://" . $shoutcast->host . ":" . $shoutcast->port);
                die();
            }
        }
    }
}
 
//pick a "random" server if the above didn't work...
$rand = rand(0, count($servers) - 1);
header("location:" . $servers[$rand]['host'] . ":" . $servers[$rand]['port']);

You will need to include shoutcast.class.php:

<?php
 
/*******************************************************************
* shoutcast.class.php
* Version: 0.1
* Author: Henrik Malmberg
* Copyright (C) 2002, Henrik Malmberg
* henrik@ih.nut
* http://yoda.ih.nu/
*
*******************************************************************
This program is free software; you can redistribute it and/or modify it
under the terms of the GNU General Public License as published by the
Free Software Foundation; either version 2 of the License, or (at your
option) any later version.
 
This program is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
General Public License for more details.
 
You should have received a copy of the GNU General Public License along
with this program; if not, write to the Free Software Foundation, Inc.,
59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
*******************************************************************/
 
class ShoutCast {
        // Public
        var $host;
        var $port;
        var $passwd;
 
        //Private
        var $_xml;
        var $_error;
 
        function openstats() {
                $fp = @fsockopen($this->host, $this->port, $errno, $errstr, 10);
                If (!$fp) {
                        $this->_error = "$errstr ($errno)";
                        return(0);
                } else {
                    fputs($fp, "GET /admin.cgi?pass=".$this->passwd."&mode=viewxml HTTP/1.0\r\n");
                    fputs($fp, "User-Agent: Mozilla\r\n\r\n");
 
					while (!feof($fp)) {
                                $this->_xml .= iconv("UTF-8","UTF-8//IGNORE",fgets($fp, 512));
                    }
                    fclose($fp);
 
                    if (stristr($this->_xml, "HTTP/1.0 200 OK") == true) {
                            // <-H> Thanks to Blaster for this fix.. trim();
                            $this->_xml = trim(substr($this->_xml, 42));
                        } else {
                                $this->_error = "Bad login";
                                return(0);
                        }
 
                        $xmlparser = xml_parser_create();
                        if (!xml_parse_into_struct($xmlparser, $this->_xml, $this->_values, $this->_indexes)) {
                                $this->_error = "Unparsable XML. The SHOUTcast server has failed to send valid XML. Since the team that wrote it has basically been disbanded by AOL, there's currently no fix (and probably never will be) for this.";
                                return(0);
                        }
 
                        xml_parser_free($xmlparser);
 
                        return(1);
                }
        }
 
        function GetCurrentListenersCount() {
                return($this->_values[$this->_indexes["CURRENTLISTENERS"][0]]["value"]);
        }
 
        function GetPeakListenersCount() {
                return($this->_values[$this->_indexes["PEAKLISTENERS"][0]]["value"]);
        }
 
        function GetMaxListenersCount() {
                return($this->_values[$this->_indexes["MAXLISTENERS"][0]]["value"]);
        }
 
        function GetReportedListenersCount() {
                return($this->_values[$this->_indexes["REPORTEDLISTENERS"][0]]["value"]);
        }
 
        function GetAverageListenTime() {
                return($this->_values[$this->_indexes["AVERAGETIME"][0]]["value"]);
        }
 
        function GetServerGenre() {
                return($this->_values[$this->_indexes["SERVERGENRE"][0]]["value"]);
        }
 
        function GetServerURL() {
                return($this->_values[$this->_indexes["SERVERURL"][0]]["value"]);
        }
 
        function GetServerTitle() {
                return($this->_values[$this->_indexes["SERVERTITLE"][0]]["value"]);
        }
 
        function GetCurrentSongTitle() {
                return($this->_values[$this->_indexes["SONGTITLE"][0]]["value"]);
        }
 
        function GetIRC() {
                return($this->_values[$this->_indexes["IRC"][0]]["value"]);
        }
 
        function GetAIM() {
                return($this->_values[$this->_indexes["AIM"][0]]["value"]);
        }
 
        function GetICQ() {
                return($this->_values[$this->_indexes["ICQ"][0]]["value"]);
        }
 
        function GetWebHitsCount() {
                return($this->_values[$this->_indexes["WEBHITS"][0]]["value"]);
        }
 
        function GetStreamHitsCount() {
                return($this->_values[$this->_indexes["STREAMHITS"][0]]["value"]);
        }
 
        function GetStreamStatus() {
                return($this->_values[$this->_indexes["STREAMSTATUS"][0]]["value"]);
        }
 
        function GetBitRate() {
                return($this->_values[$this->_indexes["BITRATE"][0]]["value"]);
        }
 
        function GetSongHistory() {
                for($i=1;$i<sizeof($this->_indexes['TITLE']);$i++) {
                        $arrhistory[$i-1] = array(
                                                                        "playedat"=>$this->_values[$this->_indexes['PLAYEDAT'][$i]]['value'],
                                                                        "title"=>$this->_values[$this->_indexes['TITLE'][$i]]['value']
                                                                );
                }
 
                return($arrhistory);
        }
 
        function GetListeners() {
                for($i=0;$i<sizeof($this->_indexes['USERAGENT']);$i++) {
                        $arrlisteners[$i] = array(
                                                                        "uid"=>$this->_values[$this->_indexes['UID'][$i]]['value'],
                                                                        "hostname"=>$this->_values[$this->_indexes['HOSTNAME'][$i]]['value'],
                                                                        "useragent"=>$this->_values[$this->_indexes['USERAGENT'][$i]]['value'],
                                                                        "underruns"=>$this->_values[$this->_indexes['UNDERRUNS'][$i]]['value'],
                                                                        "connecttime"=>$this->_values[$this->_indexes['CONNECTTIME'][$i]]['value'],
                                                                        "pointer"=>$this->_values[$this->_indexes['POINTER'][$i]]['value'],
 
                                                                );
                }
 
                return($arrlisteners);
        }
 
        function geterror() { return($this->_error); }
}

Share Printers from Windows 98 in Windows 7 Environment

I happen to have an ancient USB scanner and a LaserJet 1100 that uses a LPT port to connect. The only device I have that will connect to the printer is an ancient laptop that runs Windows 98. The goal was to have the printer shared on the network and accessible from several computers running Windows 7. This wasn’t a problem when I had the luxury of a desktop that ran Server 2008.

I quickly discovered that the normal way of adding a network printer in Windows 7 wouldn’t work with this new setup. This seems to be because of differences in how things are shared in Windows 98 versus Windows 7. After a quick Google search I learned that I’d need to add it as a Local Printer rather than a Network Printer. Initially I made a huge mistake here. When I created a new local port, I selected “Standard TCP/IP Port” rather than “Local Port.” This resulted in the printer randomly going offline. Don’t do this. Select “Local Port” and it will work beautifully. That took me a few weeks to figure out… If you’ve already made the same mistake I have, scroll to the bottom for instructions on how to clean this up.

Here’s a step-by-step:

  1. Select “Add a local printer.”
    2013-01-15 19_45_29-Add Printer
  2. Choose “Create a New Port” and select “Local Port” from the drop-down box.
    2013-01-15 19_45_48-Add Printer
    A dialog will pop up asking you for the printer’s address. I chose to use the laptop’s static IP address. The printer name is what I setup on the laptop. I’m going to assume you already have printer sharing setup in Windows 98 and know what you named the printer.
    2013-01-15 19_46_07-Port Name
  3. Choose the correct model and driver for the printer. The printer was previously used on this computer so the correct selection was already made.
    2013-01-15 19_46_20-Add Printer
    2013-01-15 19_46_27-Add Printer
  4. Give the printer a name. I’m happy with the default.
    2013-01-15 19_46_35-Add Printer
  5. The laptop is taking care of the sharing, so I’m not interested in having my client computer share the printer as well.
    2013-01-15 19_46_45-Add Printer

How to clean up printer installed as “Standard TCP/IP Port”

  1. Go to Devices and Printers.
  2. Delete the Printer that was added incorrectly (ensure the queue is empty first or it won’t delete).
  3. Select any printer. Click “Print server properties.”
  4. Go to the “Ports” tab and find the entry that corresponds to the printer that was added incorrectly. The description will be something like “Client-side rendering provider.”
  5. Delete the bad port.
  6. Close the window(s) and restart the Print Spooler (Start->run->services.msc, right click on Print Spooler service, and select restart).
  7. Repeat Steps 3 and 4. If the port is no longer present, you’re done and can proceed with the step-by-step above to add the printer correctly.

How not to power up your computer…

The power supply on my desktop began the process of dying last week. After growing tired of the random reboots and freezing, I decided to dig through my pile of old, forgotten technology in an attempt to find a “new” power supply. I came across something that looked promising and promptly hooked it up. I slid in the connectors and applied power. Suddenly the foul smell of burning electronics filled the room. Considering my past luck with power supplies (very poor), this actually wasn’t very surprising. I assumed that the power supply had simply suffered a horrible death and went on with my search for a viable replacement. I turned up nothing, and decided to get one of these based on a recommendation from a friend (and their promise to pay return shipping if the thing fails in the first year).

Two days later, the new unit arrived. I eagerly plugged it in and pressed the power button. Success! The motherboard POSTed!! Everything was fine for around 30 seconds… Then, of course, everything shut down. Naturally, all further attempts to resuscitate the beast failed. Suspecting that the power supply was bad, I arranged to take the motherboard to a friend’s house to test with a known good power supply. We plugged it in, and I shorted across the pins to turn it on. The CPU fan started spinning as expected, but then the motherboard began making sparks and magic smoke. The component pictured below is what caused the fireworks show.

I suspect that I damaged the board with my earlier scavenger hunt. Unfortunately, the “promising power supply” mentioned in the first paragraph turned out to be a proprietary mess, but wasn’t labeled as such and had normal connectors. Oops… The board may have been on its deathbed anyway, since it was burning through power supplies around every three months. Anyhow, we tested the new power supply and luckily it is still good. It seems that the safety features had engaged to prevent my house from burning down.

It should come as no surprise that I’m looking into building a new machine. The Homelab Subreddit has been interesting to read through and has provided a few ideas on what I may want to do. There is also a nice list of activities for Microsoft-based sysadmins that I’d like to work through at some point. At the very least, I’d like to be able to get at the backups, videos, and software that are currently inaccessible to me… I’m thinking that a nice SuperMicro motherboard along with a modern Xeon CPU and a healthy dose of 16gb RAM will work wonders. It’s probably overkill, but why not? A cheaper option is to find some older hardware on eBay or Craigslist, but I guess I’m too picky.

How to Make Softaculous Detect the Correct PHP Version

It appears that Softaculous is slightly broken if you have ever used the /scripts/makecpphp script to rebuild cPanel’s internal version of PHP. I found that Softaculous detects version 5.2.17 (as of cPanel 11.32) rather than the newer version (5.4.7) that I have installed. This becomes problematic when clients wish to install software that requires PHP > 5.2.17. This appears to be caused by the Softaculous installer ignoring the PHP Binary setting (uses the cPanel internal binary rather than the one specified). The ideal solution, of course, would be for this bug to be fixed on their end.

The only documentation I’ve found for this is a forum thread on the vendor’s site that doesn’t give a long-term solution. The “solution” presented in that thread is to manually set the version of PHP in a hooks file. This certainly isn’t ideal since most will forget to update the version of PHP in that file whenever PHP is upgraded. Knowing that I will definitely forget, I’ve written a little code that will take care of everything. 🙂
Continue reading

Writing a New Program to Automatically Kill Stream Rippers and Manage DJs

This is a follow-up to Automatically kick and ban “bad” listeners from your Shoutcast server and DJ management script. What the former does is kick and/or ban users from your streaming server that match certain criteria. This is useful to deter ripping of your radio stream. Right now, the only supported criteria is user agent. The latter takes care of injecting the appropriate live DJ directly into the source for Sam Broadcaster.

During my free time, I have been working on an update to this program. I’m rewriting it in C# so there will be a nice GUI and no code for end users to modify. It will only run in Windows, however. The config file(s) will be xml-based and easily read on other platforms, so there is a possibility of at least the guts running on something other than Windows.

There are several key differences between what I’m working on and the original script:

  1. Server configurations can be directly imported from Sam Broadcaster (perhaps Edcast as well depending on how it stores server credentials).
  2. DJ management will be included at some point.
  3. Support for kicking and/or banning by country and IP/subnet.
  4. Support for SHOUTcast 1 & 2 and IceCast 1 & 2.

At present, there is no ETA on this. To hold you over, here are some screenshots of the very basic, written just a few days ago program:

DJ Management Script

I present to you the script I wrote to take care of Live DJs in Sam. There are a number of different ways this could be done. If you wish to do it differently or port it to a language other than PHP, go for it (just be sure to share).

Before switching from Auto DJ to the specified Live DJ the script checks that the Live DJ is actually on the air. If the Live DJ is on the air, the Auto DJ fades to the Live DJ. If the Live DJ is not on the air when he is supposed to be, nothing happens. If the Live DJ drops the stream, the scripts switches back to the Auto DJ and rechecks every 2 seconds to see if the Live DJ has returned. If the Live DJ returns, we switch back to him.

Known issues:
* If schedule.php is inaccessible or doesn’t return the expected number of lines, Sam will crash.
* If current.txt or past.txt are inaccessible or don’t return the expected number of lines, Sam will crash.
Continue reading

Shoutcast Automatic Start Script for CentOS

I found this script and modified it a bit so it starts all three of my Shoutcast servers automatically when/if the server ever restarts. That part works great. However, there’s something I would like to change…I want to be able to manage each server individually. I’m not really sure what to do to accomplish this. At this point it’s not too big of a deal, so I’m not going to worry about it.

I want to be able to do “service shoutcast restart” and somehow be able to choose which one to restart (or restart all of them if no specific server is specified). Restarting them all isn’t acceptable when I’m only making configuration changes to one (kicking all users=bad). There is the method of hunting down the process id and killing it and then typing in the command to start it again, but that’s getting old rather fast.

I’d appreciate any suggestions.
Continue reading

XPS M1530 Audio Popping and Crackling Fix

Popping and crackling of audio on the Dell XPS M1530 has been an ongoing complaint of many users. The solution to this is rather simple, however. The problem seems to occur when the CPU throttles down. If you’re running Vista, you simply go into the advanced power options and set the minimum CPU frequency to 100% as shown in the attached images.