Zenekérő szoftver házilag

Körülmény: Adott egy tábor, ahol nagy hangsúlyt fektetnek arra, hogy legyen zene, hangosítás, stb.

Feladat: Legyen egy olyan szoftver, amivel a kedves táborozók (este bulizók) zenét tudnak kérni. Lehetőleg ezt úgy kell megvalósítani, hogy szabvány szoftverekkel működjön.

Elemzés: Két évvel ezelőtt próbálkoztunk Linuxos megvalósítással, azonban az nem viselte jól magát az ékezetes fájlnevekkel az NTFSről és egyéb helyekről átmásolt MP3-akkal.

Hardver:

  • Vegyünk egy Asus WL500gP routert, amelyre csatlakoztatjuk a külső merevlemezt. Erre azért van szükség, hogy a merevlemez "zaja" ne jusson át a hangrendszerbe (galvanikus elválasztás).
  • Vegyünk két laptopot (mi Lenovo márkájúakat választottunk, azoknak tűrhető a hangkártyája).
  • Legyen a fő laptopon telepítve: Windows, Winamp Pro, XAMPP Lite.
  • Telepítsünk a Winamp Prohoz NGWinamp kiegészítőt (amely távvezérlésre alkalmas és van hozzá PHP-s connector).
  • Töltsünk le egy MP3 lejátszót például innen.
  • Töltsük le a Prototype JS libraryt

Összköltség: 25 000 Ft (20 000 a router, pár ezer a Winamp Pro licensz. Laptopot meg elő tud keríteni az ember kölcsönbe.)

Szoftver:
A szoftver viszonylag egyszerű. A feladat egy keresőbox, amely search-as-you-type módszerrel keres, egy "preview" gomb, valamint egy lejátszási listába tevés. Ezen felül a lejátszási listát időnként frissíteni kell.

Gondok egyedül a lejátszási listába tevéssel fordultak elő, sajnos itt egy belső hívást intéztem a szoftver saját demójához, mert nem sikerült megfejteni, hogy a sajátom miért nem megy.

FIGYELEM! A szoftver erős hegesztmény, csak gyorsan össze lett dobálva, senki nem várjon el tőle tökéletes minőséget. Aki akarja, gyárthat belőle enterspájz-grade rendszert.

Először is, az SQL séma:


CREATE TABLE `songs` (
`id` int(11) NOT NULL AUTO_INCREMENT,
`name` varchar(1024) DEFAULT NULL,
`artist` varchar(255) DEFAULT NULL,
`genre` varchar(255) DEFAULT NULL,
`filename` varchar(1024) DEFAULT NULL,
`filehash` varchar(64) DEFAULT NULL,
PRIMARY KEY (`id`),
UNIQUE KEY `filehash` (`filehash`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8;

Az ehhez szükséges importáló eszköz, ami a Winamp Pro által exportált iTunes-kompatibilis XMLből beolvas. Figyeljük meg, hogy nem XML parzolás történik, hiszen egy nagyobb zeneadatbázisnál a világ összes memóriája nem elég a műveletre


<?php
mysql_connect("localhost", "root", "");
mysql_query("SET NAMES utf8");
mysql_query("USE medialibrary");
$xmlfile = fopen("import.xml", "rb");
$data = array();
while ($xmlstring = fgets($xmlfile))
{
	preg_match('/(<key>([a-zA-Z0-9 ]+)<\/key><(?:string|integer|date)>(.*?)<\/(?:string|integer|date)>)/', $xmlstring, $match);
	if (isset($match[2]))
	{
		if (($match[2] === "Track ID")
			&& count($data)
			&& strlen(trim($data['Location']))
			&& strlen(trim($data['Name']))
			&& (preg_match("/^file:/", $data['Location'])))
		{
			mysql_query("INSERT IGNORE INTO songs (name, artist, genre, filename, filehash) VALUES ('" . 
				mysql_real_escape_string($data["Name"])
				. "', '" . 
				mysql_real_escape_string($data["Artist"])
				. "', '" . 
				mysql_real_escape_string($data["Genre"])
				. "', '" . 
				mysql_real_escape_string(trim($data["Location"]))
				. "', MD5('" . 
				mysql_real_escape_string(trim($data["Location"]))
				. "'))");			
			if (!mysql_error())
			{
				echo(". ");
				flush();
				$data = array();
			} else {
				echo mysql_error();
			}
		}
		$data[$match[2]] = str_replace("\n", "\\n", str_replace("\r", "", $match[3]));
	}
}
?>

Nézzük a többi kódot:

search.php


<?php
header("Content-type:text/html; charset=utf-8");
include("ngwinamp.class.php");
$ngwc = new NGWINAMPCLIENT;
if($ngwc->connect("localhost"))
{
	if($ngwc->authenticate("") == NGWINAMP_AUTH_SUCCESS)
	{
		$tmp = $ngwc->pl_getpos();
		$pl_pos = $tmp['index'];
		$pl_length = $tmp['length'];
		$playlist = $ngwc->pl_getnames();
		$count = $pl_length - $pl_pos;
		if ($count > 15)
		{
			$full = true;
		} else {
			$full = false;
		}
	}
}

?>
<p>Maximum 15 találat. Ha mást keresel, próbáld meg szűkíteni a keresést!</p>
<?php
if ($full)
{
?>
	<p>A LEJÁTSZÁSI LISTA MEGTELT!</p>
<?php
}
?>
<table>
	<tr>
		<th> </th>
		<th>Szerző</th>
		<th> </th>
		<th>Cím</th>
		<th> </th>
	</tr>
<?php
mysql_connect("localhost", "root", "");
mysql_query("SET NAMES utf8");
mysql_query("USE medialibrary");
if ($_REQUEST['q'])
{
	$query = explode(" ", $_REQUEST['q']);
	$crit = array();
	foreach ($query as $key => $value)
	{
		if ($value == "" || $value == "-")
		{
			unset($query[$key]);
		} else {
			$crit[] = "(name LIKE '%" . mysql_real_escape_string($value) . "%' OR artist LIKE '%" . mysql_real_escape_string($value) . "%' OR genre LIKE '%" . mysql_real_escape_string($value) . "%' OR filename LIKE '%" . mysql_real_escape_string($value) . "%')";
		}
	}
	if (count($crit))
	{
		$r = mysql_query("SELECT * FROM songs WHERE " . implode(" AND ", $crit) . " LIMIT 15");
		while ($row = mysql_fetch_assoc($r)) {
			?>
				<tr>
					<td><a href="javascript:listen(<?php echo(htmlspecialchars($row['id'])); ?>)"><img src="icon-play.gif" height="24" width="24" border="0" /></a></td>
					<td><?php echo($row['artist']) ?></td>
					<td> - </td>
					<td><?php echo($row['name']) ?></td>
					<td>
					<?php
					if (!$full)
					{
					?>
						<a href="javascript:add(<?php echo(htmlspecialchars($row['id'])); ?>)">Lejátszási listába »</a>
					<?php
					}
					?>
					</td>
				</tr>
			<?php
		}
	}
}
?></table>

Mint látható, ez nem teljes HTML szerkezetet állít elő, de erre nem is lesz szükség, hiszen AJAXszal fogjuk hívni a keresést.

add.php


<?php
mysql_connect("localhost", "root", "");
mysql_query("SET NAMES utf8");
mysql_query("USE medialibrary");
header("Content-type:text/html; charset=utf-8");
include("ngwinamp.class.php");
$ngwc = new NGWINAMPCLIENT;
$id = (int)$_REQUEST['id'];
if ($id)
{
	if($ngwc->connect("localhost"))
	{
		if($ngwc->authenticate("szupertitkos") == NGWINAMP_AUTH_SUCCESS)
		{
			$tmp = $ngwc->pl_getpos();
			$pl_pos = $tmp['index'];
			$pl_length = $tmp['length'];
			$playlist = $ngwc->pl_getnames();
			$count = $pl_length - $pl_pos;
			
			/*if ($count > 15)
			{*/
				$r = mysql_query("SELECT filename FROM songs WHERE id=" . $id . " LIMIT 1");
				while ($row = mysql_fetch_assoc($r))
				{
					$filename = urldecode(str_replace("file://localhost/", "", $row['filename']));
					header("X-Added-File: " . stripslashes($filename));
					//echo $ngwc->pl_addfiles(array(stripslashes($filename)));
					file_get_contents("http://localhost/smalltest.php?host=localhost&passwd=szupertitkosaction=browse&bw_op=add&bw_addfile=" . urlencode($filename) . "&bw_maxcount=50");
				}
			/*}*/
		} else {
			header("HTTP/1.1 503 Winamp not running");
		}
		$ngwc->disconnect();
	} else {
		header("HTTP/1.1 503 Winamp not running");
	}
}
?>

playlist.php


<?php
header("Content-type:text/html; charset=ISO-8859-1");
include("ngwinamp.class.php");
$ngwc = new NGWINAMPCLIENT;
if($ngwc->connect("localhost"))
{
	if($ngwc->authenticate("") == NGWINAMP_AUTH_SUCCESS)
	{
		$tmp = $ngwc->pl_getpos();
		$pl_pos = $tmp['index'];
		$pl_length = $tmp['length'];
		$playlist = $ngwc->pl_getnames();
		?>
			<table>
				<tr>
					<th>Lejátszási lista</th>
					<th></th>
				</tr>
				<?php
					for ($i = $pl_pos; $i<$pl_length + 1; $i++)
					{
				?>
					<tr>
						<td><?php echo(htmlspecialchars($playlist[$i])); ?></td>
					</tr>
				<?php
					}
				?>
			</table>
		<?php
	} else {
		header("HTTP/1.1 503 Winamp not running");
	}
} else {
	header("HTTP/1.1 503 Winamp not running");
}
?>

player.php
Ez nyitja meg a Flash-es MP3 lejátszót.


<object type="application/x-shockwave-flash" data="player_mp3_maxi.swf" width="200" height="20">
     <param name="movie" value="player_mp3_maxi.swf" />
     <param name="FlashVars" value="autoplay=1&mp3=play.php?id=<?php echo(urlencode($_GET['id'])) ?>" />
</object>

play.php
Ez küldi le magát az MP3 fájlt.


<?php
mysql_connect("localhost", "root", "");
mysql_query("SET NAMES utf8");
mysql_query("USE medialibrary");
$id = (int)$_REQUEST['id'];
if ($id)
{
	header("X-Song-Id: " . $id);
	$r = mysql_query("SELECT filename FROM songs WHERE id=" . $id . " LIMIT 1");
	while ($row = mysql_fetch_assoc($r))
	{
		$filename = urldecode(str_replace("file://localhost/", "", $row['filename']));
		header('Content-type: audio/mp3');
		header('Content-length: ' . filesize($filename));
		echo(file_get_contents($filename));
	}
}
?>

index.php
Ez csinálja az AJAX hívásokat.


<?php
	header("Content-type:text/html; charset=utf-8");
?>
<html>
	<head>
		<title>Csibetábor rádió</title>
		<script type="text/javascript" src="prototype.js"></script>
		<script type="text/javascript">
			var lastsearch="";
			var t;
			var sadd = true;
			var tadd;
		
			function listen(id)
			{
				new Ajax.Updater('player', 'player.php?id=' + id, {asynchronous:true});
			}

			function add(id)
			{
				if (sadd)
				{
					new Ajax.Request('add.php?id=' + id, {asynchronous:true, onComplete: refreshPlaylist() });
					sadd = false;
					tadd = setTimeout("addEnable()", 300000);
				} else {
					alert("5 percenként maximum egy számot adhatsz hozzá!");
				}
			}
			
			function addEnable()
			{
				sadd = true;
			}
			
			function refreshPlaylist()
			{
				new Ajax.Updater('playlist', 'playlist.php', { method: 'get' });
				search();
			}
			
			function search()
			{
				if (lastsearch != $('q').value)
				{
					lastsearch = $('q').value;
					var params = Form.serialize($('search'));
					new Ajax.Updater('searchresults', 'search.php', {asynchronous:true, parameters:params});
				}
				$('q').focus();
				return false;
			}
			
			t = setInterval("search()", 500);
		</script>
		<style>
			td {vertical-align: top;}
		</style>
	</head>

	<body onkeydown="return true;">
		<table width="100%" height="100%">
			<tr><td colspan="2" width="100%" height="170"><img src="logo3.gif" /></td></tr>
			<tr>
				<td id="td_search" width="60%">
					<form id="search" onsubmit="return search();">
						<label for="q">Keresés:</label>
						<input type="text" name="q" id="q" value="" />
					</form>
					<script type="text/javascript">
						$('q').observe('keydown', search);
						$('q').observe('change', search);
						new Ajax.Updater('searchresults', 'search.php', {asynchronous:true});
					</script>
					<p>5 percenként maximum egy számot tudsz betenni!</p>
				</td>
				<td id="td_player">
					<div id="player">
					</div>
				</td>
			</tr>
			<tr height="80%">
				<td id="td_searchresults" style="position:relative;">
					<div id="searchresults" style="height:100%; width:100%;overflow:auto;">
					</div>
				</td>
				<td id="rightside" style="position:relative;">
					<div id="playlist">
					</div>
					<script type="text/javascript">
						new Ajax.PeriodicalUpdater('playlist', 'playlist.php', { method: 'get', frequency: 2, decay: 0 });
					</script>
				</td>
			</tr>
		</table>
	</body>
</html>

Remélem, nem rontottam el semmit a bemásolásnál. A szoftver nem túl bonyolult, lehet nekiesni átírni.

További tippek:

  • Winamp távirányítás: iAMPRemote vagy VNC (ez utóbbit használtuk a helyileg másol levő műsorok vezérléséhez)
  • Bulihoz tessék kivenni az 5 perces limitet és helyette egy másik laptopon bejátszani a zenéket. (Ennek nagy sikere volt.)

Hozzászólások

Sajnos az ékezetes betűs parát az sem oldotta meg (konkrétan ezzel is próbálkoztunk két évvel ezelőtt), arról nem is beszélve, hogy ugyanúgy meg kellett volna írni a lebutított UI-t, akkor meg már egyszerűbb volt megírni Winampra, azt legalább rajtam kívül más is tudja konfigurálgatni.

Alternatív megoldás:

Linux-ra fel kell rakni egy SqueezeCenter -t. (Ez a SqueezeBox -ok szerver-szoftvere, nyílt teljesen.)
(apt-get install squeezecenter)

A klienseken pedig winamp-pal lehet csatlakozni a streamhez a http://szerver-ip:9000/stream.mp3 url segítségével.
A lejátszás a SqueezeCenter webes felületéről vezérelhető. Szimultál több klienset is lehet vezérelni, úgy, hogy midegyiken más szóljon, de egy kattintással szinkronizálható a lejátszás, és akkor mindegyiken ugyan az szól, nincs időcsúszás sem.

A SqueezeCenter webbes felületéről letölthető egy kis java program, a SoftSqueeze. Ez egy szoftveres SqueezeBox.
Ha nem akarsz winamppal szarakodni, elindítod ezt a klienseken, és úgy csatlakoznak a SqueezeCenter-hez, mint egy igazi SqueezeBox, és akkor a kliensekről is lehet számot választani, irányítani a lejátszást, nem kell ehhez a webes felületre lépni (noha a webes felület is nagyon kényelmes, teljesen jól használható, lehet könyvtár-nézet mellett iTues-szerűen előadó, műfaj, stb.stb. szerint is látni a zenéket.)

Passz, videóra én NAS+Porcorn Hour lejátszó kombókat használok.
Videóra nem tudom, hogy van-e ilyen komplett streaming megoldás, esetleg abban lehet gondolkozni, hogy VLC-vel streameled a cuccost, másik oldalon meg VLC-vel veszed...
De akkor már egyszerűbb, ha a videókat hálózaton (smb/nfs/afp/akármi) éred el, és ahol meg kell jeleníteni, ott játsszod le pl. vlc-vel, vagy akár célhardverrel, pl. popcorn hour lejátszóval.

Firefly Media Server linuxra, + iTunes / Rhythmbox kliens.
--


()=() Ki oda vagyik,
('Y') hol szall a galamb
C . C elszalasztja a
()_() kincset itt alant.