diff --git a/bin/channel_run.inc b/bin/channel_run.inc
index 8c18312b..ca29f1be 100644
--- a/bin/channel_run.inc
+++ b/bin/channel_run.inc
@@ -23,6 +23,8 @@
define('NO_SESSION','1');
define('CLI', 1);
+$chunk_buffer = '';
+$nb_chunks_remainder = 0;
$path = dirname(__FILE__);
$prefix = realpath($path . '/../');
require_once $prefix . '/lib/init.php';
@@ -42,11 +44,11 @@ if ($cargv > 1) {
if ($_SERVER['argv'][$x] == "-c" && ($x + 1) < $cargv) {
$chanid = intval($_SERVER['argv'][++$x]);
$operations_string .= "\n\t" . T_('- Channel ' . $chanid);
- }
+ }
elseif ($_SERVER['argv'][$x] == "-v") {
$operations_string .= "\n\t" . T_('- Verbose');
$verbose = true;
- }
+ }
elseif ($_SERVER['argv'][$x] == "-p" && ($x + 1) < $cargv) {
$port = intval($_SERVER['argv'][++$x]);
$operations_string .= "\n\t" . T_('- Port ' . $port);
@@ -56,7 +58,7 @@ if ($cargv > 1) {
if ($chanid <= 0) {
usage();
- exit;
+ exit;
}
// Transcode is mandatory to have consistent stream codec
@@ -66,7 +68,7 @@ if ($transcode_cfg == 'never') {
die('Cannot start channel, transcoding is mandatory to work.');
}
-echo T_("Starting Channel...") . $operations_string . "\n";
+echo T_("Starting Channel...") . $operations_string . "\n";
$channel = new Channel($chanid);
if (!$channel->id) {
@@ -106,7 +108,7 @@ echo T_("Listening on ") . $address . ':' . $port . "\n";
$stream_clients = array();
$client_socks = array();
-$last_stream = 0;
+$last_stream = microtime(true);
while(true)
{
//prepare readable sockets
@@ -114,7 +116,6 @@ while(true)
if (count($client_socks) < $channel->max_listeners) {
$read_socks[] = $server;
}
-
//echo "b\n";ob_flush();
//start reading and use a large timeout
if(stream_select ( $read_socks, $write, $except, 1))
@@ -123,7 +124,7 @@ while(true)
if (in_array($server, $read_socks))
{
$new_client = stream_socket_accept($server);
-
+
if ($new_client)
{
debug_event('channel', 'Connection accepted from ' . stream_socket_get_name($new_client, true) . '.', '5');
@@ -133,287 +134,120 @@ while(true)
echo "New client connected.\n";
ob_flush();
}
-
+
//delete the server socket from the read sockets
unset($read_socks[array_search($server, $read_socks)]);
}
-
+
// Get new message from existing client
foreach($read_socks as $sock)
{
- $data = fread($sock, 1024);
- if(!$data)
- {
- client_disconnect($channel, $client_socks, $stream_clients, $sock);
- continue;
- }
-
- $headers = explode("\n", $data);
-
- if (count($headers) > 0) {
- $cmd = explode(" ", $headers[0]);
- if ($cmd['0'] == 'GET') {
- switch ($cmd['1']) {
- case '/stream.' . $channel->stream_type:
- $options = array(
- 'socket' => $sock,
- 'length' => 0
- );
-
- for ($i = 1; $i < count($headers); $i++) {
- $headerpart = explode(":", $headers[$i], 2);
- $header = strtolower(trim($headerpart[0]));
- $value = trim($headerpart[1]);
- switch ($header) {
- case 'icy-metadata':
- $options['metadata'] = ($value == '1');
- $options['metadata_lastsent'] = 0;
- $options['metadata_lastsong'] = 0;
- break;
- }
- }
-
- // Stream request
- if ($options['metadata']) {
- //fwrite($sock, "ICY 200 OK\r\n");
- fwrite($sock, "HTTP/1.0 200 OK\r\n");
- } else {
- fwrite($sock, "HTTP/1.1 200 OK\r\n");
- fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
- }
- fwrite($sock, "Content-Type: " . Song::type_to_mime($channel->stream_type) . "\r\n");
- fwrite($sock, "Accept-Ranges: none\r\n");
-
- $genre = $channel->get_genre();
- // Send Shoutcast metadata on demand
- if ($options['metadata']) {
- fwrite($sock, "icy-notice1: " . AmpConfig::get('site_title') . "\r\n");
- fwrite($sock, "icy-name: " . $channel->name . "\r\n");
- if (!empty($genre)) {
- fwrite($sock, "icy-genre: " . $genre . "\r\n");
- }
- fwrite($sock, "icy-url: " . $channel->url . "\r\n");
- fwrite($sock, "icy-pub: " . ($channel->is_private) ? '0' : '1' . "\r\n");
- if ($channel->bitrate) {
- fwrite($sock, "icy-br: " . strval($channel->bitrate) . "\r\n");
- }
- fwrite($sock, "icy-metaint: " . strval($metadata_interval) . "\r\n");
- }
- // Send additional Icecast metadata
- fwrite($sock, "x-audiocast-server-url: " . $channel->url . "\r\n");
- fwrite($sock, "x-audiocast-name: " . $channel->name . "\r\n");
- fwrite($sock, "x-audiocast-description: " . $channel->description . "\r\n");
- fwrite($sock, "x-audiocast-url: " . $channel->url . "\r\n");
- if (!empty($genre)) {
- fwrite($sock, "x-audiocast-genre: " . $genre . "\r\n");
- }
- fwrite($sock, "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n");
- fwrite($sock, "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n");
-
- fwrite($sock, "\r\n");
-
- // Add to stream clients list
- $key = array_search($sock, $read_socks);
- $stream_clients[$key] = $options;
- break;
-
- case '/':
- case '/status.xsl':
- // Stream request
- fwrite($sock, "HTTP/1.0 200 OK\r\n");
- fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
- fwrite($sock, "Content-Type: text/html\r\n");
- fwrite($sock, "\r\n");
-
- // Create xsl structure
-
- // Header
- $xsl = "";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Icecast Streaming Media Server - Ampache" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
-
- // Content
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "

" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Stream Title: | " . "\n";
- $xsl .= "" . $channel->name . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Stream Description: | " . "\n";
- $xsl .= "" . $channel->description . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Content Type: | " . "\n";
- $xsl .= "" . Song::type_to_mime($channel->stream_type) . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Mount Start: | " . "\n";
- $xsl .= "" . date("c", $channel->start_date) . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Bitrate: | " . "\n";
- $xsl .= "" . $channel->bitrate . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Current Listeners: | " . "\n";
- $xsl .= "" . $channel->listeners . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Peak Listeners: | " . "\n";
- $xsl .= "" . $channel->peak_listeners . " | " . "\n";
- $xsl .= "
" . "\n";
- $genre = $channel->get_genre();
- $xsl .= "" . "\n";
- $xsl .= "Stream Genre: | " . "\n";
- $xsl .= "" . $genre . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "Stream URL: | " . "\n";
- $xsl .= "url . "\" target=\"_blank\">" . $channel->url . " | " . "\n";
- $xsl .= "
" . "\n";
- $currentsong = "";
- if ($channel->media) {
- $currentsong = $channel->media->f_artist . " - " . $channel->media->f_title;
- }
- $xsl .= "" . "\n";
- $xsl .= "Current Song: | " . "\n";
- $xsl .= "" . $currentsong . " | " . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "

" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
-
- // Footer
- $xsl .= "
" . "\n";
- $xsl .= "Support Icecast development at
www.icecast.org" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "
" . "\n";
- $xsl .= "" . "\n";
- $xsl .= "" . "\n";
-
- fwrite($sock, $xsl);
-
- fclose($sock);
- unset($client_socks[array_search($sock, $client_socks)]);
- break;
-
- case '/style.css':
- case '/favicon.ico':
- case '/images/corner_bottomleft.jpg':
- case '/images/corner_bottomright.jpg':
- case '/images/corner_topleft.jpg':
- case '/images/corner_topright.jpg':
- case '/images/icecast.png':
- case '/images/key.png':
- case '/images/tunein.png':
- // Get read file data
- $fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1'];
- $pinfo = pathinfo($fpath);
-
- $content_type = 'text/html';
- switch ($pinfo['extension']) {
- case 'css':
- $content_type = "text/css";
- break;
- case 'jpg':
- $content_type = "image/jpeg";
- break;
- case 'png':
- $content_type = "image/png";
- break;
- case 'ico':
- $content_type = "image/vnd.microsoft.icon";
- break;
- }
- fwrite($sock, "HTTP/1.0 200 OK\r\n");
- fwrite($sock, "Content-Type: " . $content_type . "\r\n");
- $fdata = file_get_contents($fpath);
- fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n");
- fwrite($sock, "\r\n");
- fwrite($sock, $fdata);
- fclose($sock);
- unset($client_socks[array_search($sock, $client_socks)]);
- break;
- case '/stream.' . $channel->stream_type . '.m3u':
- fwrite($sock, "HTTP/1.0 200 OK\r\n");
- fwrite($sock, "Cache-control: public\r\n");
- fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n");
- fwrite($sock, "Content-Type: audio/x-mpegurl\r\n");
- fwrite($sock, "\r\n");
-
- fwrite($sock, $channel->get_stream_url() . "\n");
-
- fclose($sock);
- unset($client_socks[array_search($sock, $client_socks)]);
- break;
- default:
- debug_event('channel', 'Unknown request. Closing connection.', '3');
- fclose($sock);
- unset($client_socks[array_search($sock, $client_socks)]);
- break;
- }
- }
- }
// Handle data parse
+ http_serve($channel, $client_socks, $stream_clients, $read_socks, $sock);
}
}
-
+
if ($channel->bitrate) {
+
$time_offset = microtime(true) - $last_stream;
- $mtime = ($last_stream > 0 && $time_offset < 1) ? $time_offset : 1;
- if ($last_stream > 0 && $time_offset < 1) {
- usleep(1000000 - ($time_offset * 1000000));
- } elseif ($last_stream > 0) {
- //$mtime = $time_offset;
+
+ //debug_event('channel', 'time_offset : '. $time_offset, '5');
+ //debug_event('channel', 'last_stream: '.$last_stream, '5');
+
+ if ($time_offset < 1)
+ usleep(1000000 - ($time_offset * 1000000)); // always at least 1 second between cycles
+
+ $last_stream = microtime(true);
+ $mtime = ($time_offset > 1) ? $time_offset : 1;
+ $nb_chunks = ceil(($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8) / $channel->chunk_size); // channel->bitrate+1% ... leave some headroom for metadata / headers
+
+ // we only send full blocks, save remainder and apply when appropriate: allows more granular/arbitrary average bitrates
+ if ($nb_chunks - ($mtime * ($channel->bitrate+1/100*$channel->bitrate) * 1000 / 8 / $channel->chunk_size) > 0)
+ $nb_chunks_remainder += $nb_chunks - ($mtime * $channel->bitrate * 1000 / 8 / $channel->chunk_size);
+ if ($nb_chunks >= 1 && $nb_chunks_remainder >= 1){
+ $nb_chunks -= 1;
+ $nb_chunks_remainder -= 1;
+ //debug_event('channel', 'REMAINDER: '.$nb_chunks_remainder, '5');
}
- $nb_chunks = ceil(($mtime * $channel->bitrate * 1000) / 4096);
+ //debug_event('channel', 'mtime '.$mtime, '5');
+ //debug_event('channel', 'nb_chunks: '.$nb_chunks, '5');
+
} else {
$nb_chunks = 1;
}
-
+
// Get multiple chunks according to bitrate to return enough data per second (because sleep with socket select)
for ($c = 0; $c < $nb_chunks; $c++) {
+
$chunk = $channel->get_chunk();
$chunklen = strlen($chunk);
+ $chunk_buffer .= $chunk;
+
+ //buffer maintenance
+ while (strlen($chunk_buffer) > (15 * $nb_chunks * $channel->chunk_size) ){ // buffer 15 seconds
+
+ if (strtolower($channel->stream_type) == "ogg" && strtohex(substr($chunk_buffer, 0, 4)) == "4F676753") { //maintain ogg chunk alignment --- "4F676753" == "OggS"
+ // read OggS segment length
+ $hex = strtohex(substr($chunk_buffer, 0, 27));
+ $ogg_nr_of_segments = hexdec(substr($hex, 26*2, 2));
+ $hex .= strtohex(substr($chunk_buffer, 27, $ogg_nr_of_segments));
+ $ogg_sum_segm_laces = 0;
+ for($segm = 0; $segm < $ogg_nr_of_segments; $segm++){
+ $ogg_sum_segm_laces += hexdec(substr($hex, 27*2 + $segm*2, 2));
+ }
+ //$naive = strpos(substr($chunk_buffer, 4), 'OggS') + 4; // naive search for next header
+ //remove 1 whole OggS chunk
+ $chunk_buffer = substr($chunk_buffer, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
+ //debug_event('channel', '$new chunk buffer : '.substr($chunk_buffer,0,300) . ' $hex: '.strtohex(substr($chunk_buffer,0,600)) . ' $ogg_nr_of_segments: ' .$ogg_nr_of_segments . ' bytes cut off: '.(27 + $ogg_nr_of_segments + $ogg_sum_segm_laces) . ' naive: ' .$naive, '5');
+ } elseif (strtolower($channel->stream_type) == "ogg") {
+ debug_event('channel', 'Ogg alignament broken! Trying repair...', '5');
+ $manual_search = strpos($chunk_buffer, 'OggS');
+ $chunk_buffer = substr($chunk_buffer, $manual_search);
+ } else { // no chunk alignment required
+ $chunk_buffer = substr($chunk_buffer, $chunklen);
+ }
+ //debug_event('channel', 'remvd chunk from buffer ', '5');
+ }
+
if ($chunklen > 0) {
foreach($stream_clients as $key => $client)
{
$sock = $client['socket'];
+ $clchunk = $chunk;
+
if(!is_resource($sock)) {
client_disconnect($channel, $client_socks, $stream_clients, $sock);
continue;
}
-
- $clchunk = $chunk;
+
+ if ($client['isnew'] == 1){
+ $client['isnew'] = 0;
+ //fwrite($sock, $channel->header_chunk);
+ //debug_event('channel', 'IS NEW' . $channel->header_chunk, '5');
+ $clchunk_buffer = $channel->header_chunk . $chunk_buffer;
+ if ($client['metadata']){ //stub
+ //if (strtolower($channel->stream_type) == "ogg")
+ while(strlen($clchunk_buffer) > $metadata_interval){
+ fwrite($sock, substr($clchunk_buffer, 0, $metadata_interval) . chr(0x00));
+ $clchunk_buffer = substr($clchunk_buffer, $metadata_interval);
+ }
+ fwrite($sock, $clchunk_buffer);
+ $client['metadata_lastsent'] = 0;
+ $client['length'] += strlen($clchunk_buffer);
+ } else {
+ //fwrite($sock, $channel->header_chunk);
+ $buffer_bytes_written = fwrite($sock, $clchunk_buffer);
+ while ($buffer_bytes_written != strlen($clchunk_buffer)){
+ debug_event('channel', 'I HERPED WHEN I SHOULD HAVE DERPED!', '5');
+ //debug_event('channel', 'chunk_buffer bytes written:' .$buffer_bytes_written .'strlen $chunk_buffer: '.strlen($chunk_buffer), '5');
+ $clchunk_buffer = substr($clchunk_buffer, $buffer_bytes_written);
+ $buffer_bytes_written = fwrite($sock, $clchunk_buffer);
+ }
+ }
+ $stream_clients[$key] = $client;
+ continue;
+ }
+
// Check if we need to insert metadata information
if ($client['metadata']) {
$chkmdlen = ($client['length'] + $chunklen) - $client['metadata_lastsent'];
@@ -436,11 +270,11 @@ while(true)
$clchunk = substr($chunk, $subpos);
}
}
+
if (strlen($clchunk) > 0) {
fwrite($sock, $clchunk);
$client['length'] += strlen($clchunk);
}
-
$stream_clients[$key] = $client;
//debug_event('channel', 'Client stream current length: ' . $client['length'], '5');
}
@@ -448,12 +282,10 @@ while(true)
$channel->update_listeners(0);
die('No more data, stream ended.');
}
-
- $last_stream = microtime(true);
}
}
-ob_end_flush();
+ob_end_flush();
echo "\n";
function client_disconnect($channel, &$client_socks, &$stream_clients, $sock)
@@ -473,7 +305,7 @@ function usage()
echo T_("- Channel Listening -");
echo "\n";
echo T_("Usage: channel_run.inc [-c {CHANNEL ID}|-p {PORT}|-v]");
- echo "\n\t";
+ echo "\n\t";
echo "\n-c {CHANNEL ID}\t";
echo T_('Channel id to start');
echo "\n-p {PORT}\t";
@@ -485,4 +317,261 @@ function usage()
echo "\n";
}
+function http_serve($channel, &$client_socks, &$stream_clients, &$read_socks, $sock)
+{
+ $data = fread($sock, 1024);
+ if(!$data)
+ {
+ client_disconnect($channel, $client_socks, $stream_clients, $sock);
+ return;
+ }
+
+ $headers = explode("\n", $data);
+
+ if (count($headers) > 0) {
+ $cmd = explode(" ", $headers[0]);
+ if ($cmd['0'] == 'GET') {
+ switch ($cmd['1']) {
+ case '/stream.' . $channel->stream_type:
+ $options = array(
+ 'socket' => $sock,
+ 'length' => 0,
+ 'isnew' => 1
+ );
+
+ //debug_event('channel', 'HTTP HEADERS: '.$data,'5');
+ for ($i = 1; $i < count($headers); $i++) {
+ $headerpart = explode(":", $headers[$i], 2);
+ $header = strtolower(trim($headerpart[0]));
+ $value = trim($headerpart[1]);
+ switch ($header) {
+ case 'icy-metadata':
+ $options['metadata'] = ($value == '1');
+ $options['metadata_lastsent'] = 0;
+ $options['metadata_lastsong'] = 0;
+ break;
+ }
+ }
+
+ // Stream request
+ if ($options['metadata']) {
+ //$http = "ICY 200 OK\r\n");
+ $http = "HTTP/1.0 200 OK\r\n";
+ } else {
+ $http = "HTTP/1.1 200 OK\r\n";
+ $http .= "Cache-Control: no-store, no-cache, must-revalidate\r\n";
+ }
+ $http .= "Content-Type: " . Song::type_to_mime($channel->stream_type) . "\r\n";
+ $http .= "Accept-Ranges: none\r\n";
+
+ $genre = $channel->get_genre();
+ // Send Shoutcast metadata on demand
+ //if ($options['metadata']) {
+ $http .= "icy-notice1: " . AmpConfig::get('site_title') . "\r\n";
+ $http .= "icy-name: " . $channel->name . "\r\n";
+ if (!empty($genre)) {
+ $http .= "icy-genre: " . $genre . "\r\n";
+ }
+ $http .= "icy-url: " . $channel->url . "\r\n";
+ $http .= "icy-pub: " . (($channel->is_private) ? "0" : "1") . "\r\n";
+ if ($channel->bitrate) {
+ $http .= "icy-br: " . strval($channel->bitrate) . "\r\n";
+ }
+ global $metadata_interval;
+ $http .= "icy-metaint: " . strval($metadata_interval) . "\r\n";
+ //}
+ // Send additional Icecast metadata
+ $http .= "x-audiocast-server-url: " . $channel->url . "\r\n";
+ $http .= "x-audiocast-name: " . $channel->name . "\r\n";
+ $http .= "x-audiocast-description: " . $channel->description . "\r\n";
+ $http .= "x-audiocast-url: " . $channel->url . "\r\n";
+ if (!empty($genre)) {
+ $http .= "x-audiocast-genre: " . $genre . "\r\n";
+ }
+ $http .= "x-audiocast-bitrate: " . strval($channel->bitrate) . "\r\n";
+ $http .= "x-audiocast-public: " . (($channel->is_private) ? "0" : "1") . "\r\n";
+
+ $http .= "\r\n";
+
+ fwrite($sock, $http);
+
+ // Add to stream clients list
+ $key = array_search($sock, $read_socks);
+ $stream_clients[$key] = $options;
+ break;
+
+ case '/':
+ case '/status.xsl':
+ // Stream request
+ fwrite($sock, "HTTP/1.0 200 OK\r\n");
+ fwrite($sock, "Cache-Control: no-store, no-cache, must-revalidate\r\n");
+ fwrite($sock, "Content-Type: text/html\r\n");
+ fwrite($sock, "\r\n");
+
+ // Create xsl structure
+
+ // Header
+ $xsl = "";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Icecast Streaming Media Server - Ampache" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+
+ // Content
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "

" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Stream Title: | " . "\n";
+ $xsl .= "" . $channel->name . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Stream Description: | " . "\n";
+ $xsl .= "" . $channel->description . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Content Type: | " . "\n";
+ $xsl .= "" . Song::type_to_mime($channel->stream_type) . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Mount Start: | " . "\n";
+ $xsl .= "" . date("c", $channel->start_date) . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Bitrate: | " . "\n";
+ $xsl .= "" . $channel->bitrate . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Current Listeners: | " . "\n";
+ $xsl .= "" . $channel->listeners . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Peak Listeners: | " . "\n";
+ $xsl .= "" . $channel->peak_listeners . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $genre = $channel->get_genre();
+ $xsl .= "" . "\n";
+ $xsl .= "Stream Genre: | " . "\n";
+ $xsl .= "" . $genre . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "Stream URL: | " . "\n";
+ $xsl .= "url . "\" target=\"_blank\">" . $channel->url . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $currentsong = "";
+ if ($channel->media) {
+ $currentsong = $channel->media->f_artist . " - " . $channel->media->f_title;
+ }
+ $xsl .= "" . "\n";
+ $xsl .= "Current Song: | " . "\n";
+ $xsl .= "" . $currentsong . " | " . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "

" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+
+ // Footer
+ $xsl .= "
" . "\n";
+ $xsl .= "Support Icecast development at
www.icecast.org" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "
" . "\n";
+ $xsl .= "" . "\n";
+ $xsl .= "" . "\n";
+
+ fwrite($sock, $xsl);
+
+ fclose($sock);
+ unset($client_socks[array_search($sock, $client_socks)]);
+ break;
+
+ case '/style.css':
+ case '/favicon.ico':
+ case '/images/corner_bottomleft.jpg':
+ case '/images/corner_bottomright.jpg':
+ case '/images/corner_topleft.jpg':
+ case '/images/corner_topright.jpg':
+ case '/images/icecast.png':
+ case '/images/key.png':
+ case '/images/tunein.png':
+ // Get read file data
+ $fpath = AmpConfig::get('prefix') . '/channel' . $cmd['1'];
+ $pinfo = pathinfo($fpath);
+
+ $content_type = 'text/html';
+ switch ($pinfo['extension']) {
+ case 'css':
+ $content_type = "text/css";
+ break;
+ case 'jpg':
+ $content_type = "image/jpeg";
+ break;
+ case 'png':
+ $content_type = "image/png";
+ break;
+ case 'ico':
+ $content_type = "image/vnd.microsoft.icon";
+ break;
+ }
+ fwrite($sock, "HTTP/1.0 200 OK\r\n");
+ fwrite($sock, "Content-Type: " . $content_type . "\r\n");
+ $fdata = file_get_contents($fpath);
+ fwrite($sock, "Content-Length: " . strlen($fdata) . "\r\n");
+ fwrite($sock, "\r\n");
+ fwrite($sock, $fdata);
+ fclose($sock);
+ unset($client_socks[array_search($sock, $client_socks)]);
+ break;
+ case '/stream.' . $channel->stream_type . '.m3u':
+ fwrite($sock, "HTTP/1.0 200 OK\r\n");
+ fwrite($sock, "Cache-control: public\r\n");
+ fwrite($sock, "Content-Disposition: filename=stream." . $channel->stream_type . ".m3u\r\n");
+ fwrite($sock, "Content-Type: audio/x-mpegurl\r\n");
+ fwrite($sock, "\r\n");
+
+ fwrite($sock, $channel->get_stream_url() . "\n");
+
+ fclose($sock);
+ unset($client_socks[array_search($sock, $client_socks)]);
+ break;
+ default:
+ debug_event('channel', 'Unknown request. Closing connection.', '3');
+ fclose($sock);
+ unset($client_socks[array_search($sock, $client_socks)]);
+ break;
+ }
+ }
+ }
+}
+
+function strtohex($x) {
+ $s='';
+ foreach(str_split($x) as $c) $s.=sprintf("%02X",ord($c));
+ return($s);
+}
+
?>
diff --git a/lib/class/channel.class.php b/lib/class/channel.class.php
index 3bbae312..bdc03a49 100644
--- a/lib/class/channel.class.php
+++ b/lib/class/channel.class.php
@@ -39,6 +39,10 @@ class Channel extends database_object implements media, library_item
public $name;
public $description;
+ public $header_chunk;
+ public $chunk_size = 4096;
+ private $header_chunk_remainder = 0;
+
public $tags;
public $f_tags;
@@ -162,10 +166,10 @@ class Channel extends database_object implements media, library_item
switch ($type) {
case 'playlist':
$ftype = $type;
- break;
+ break;
default:
$ftype = '';
- break;
+ break;
}
return $ftype;
@@ -221,9 +225,9 @@ class Channel extends database_object implements media, library_item
$medias = array();
if (!$filter_type || $filter_type == 'channel') {
$medias[] = array(
- 'object_type' => 'channel',
- 'object_id' => $this->id
- );
+ 'object_type' => 'channel',
+ 'object_id' => $this->id
+ );
}
return $medias;
}
@@ -412,17 +416,54 @@ class Channel extends database_object implements media, library_item
// Stream not yet initialized for this media, start it
if (!$this->transcoder) {
$options = array(
- 'bitrate' => $this->bitrate
- );
+ 'bitrate' => $this->bitrate
+ );
$this->transcoder = Stream::start_transcode($this->media, $this->stream_type, null, $options);
$this->media_bytes_streamed = 0;
}
if (is_resource($this->transcoder['handle'])) {
-
- $chunk = fread($this->transcoder['handle'], 4096);
+ if (ftell($this->transcoder['handle']) == 0)
+ $this->header_chunk = '';
+ $chunk = fread($this->transcoder['handle'], $this->chunk_size);
$this->media_bytes_streamed += strlen($chunk);
+ if ((ftell($this->transcoder['handle']) < 10000 && strtolower($this->stream_type) == "ogg") || $this->header_chunk_remainder) {
+ //debug_event('channel', 'File handle pointer: ' . ftell($this->transcoder['handle']) ,'5');
+ $clchunk = $chunk;
+
+ if ($this->header_chunk_remainder) {
+ $this->header_chunk .= substr($clchunk, 0, $this->header_chunk_remainder);
+ if (strlen($clchunk) >= $this->header_chunk_remainder) {
+ $clchunk = substr($clchunk, $this->header_chunk_remainder);
+ $this->header_chunk_remainder = 0;
+ } else {
+ $this->header_chunk_remainder = $this->header_chunk_remainder - strlen($clchunk);
+ $clchunk = '';
+ }
+ }
+ // see bin/channel_run.inc for explanation what's happening here
+ while ($this->strtohex(substr($clchunk, 0, 4)) == "4F676753") {
+ $hex = $this->strtohex(substr($clchunk, 0, 27));
+ $ogg_nr_of_segments = hexdec(substr($hex, 26*2, 2));
+ if ((substr($clchunk, 27 + $ogg_nr_of_segments + 1, 6) == "vorbis") || (substr($clchunk, 27 + $ogg_nr_of_segments, 4) == "Opus")) {
+ $hex .= $this->strtohex(substr($clchunk, 27, $ogg_nr_of_segments));
+ $ogg_sum_segm_laces = 0;
+ for ($segm = 0; $segm < $ogg_nr_of_segments; $segm++) {
+ $ogg_sum_segm_laces += hexdec(substr($hex, 27*2 + $segm*2, 2));
+ }
+ $this->header_chunk .= substr($clchunk, 0, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
+ if (strlen($clchunk) < (27 + $ogg_nr_of_segments + $ogg_sum_segm_laces))
+ $this->header_chunk_remainder = (int) (27 + $ogg_nr_of_segments + $ogg_sum_segm_laces - strlen($clchunk));
+ $clchunk = substr($clchunk, 27 + $ogg_nr_of_segments + $ogg_sum_segm_laces);
+ } else //no more interesting headers
+ $clchunk = '';
+ }
+ }
+ //debug_event('channel', 'File handle pointer: ' . ftell($this->transcoder['handle']) ,'5');
+ //debug_event('channel', 'CHUNK : ' . $chunk, '5');
+ //debug_event('channel', 'Chunk size: ' . strlen($chunk) ,'5');
+
// End of file, prepare to move on for next call
if (feof($this->transcoder['handle'])) {
$this->media->set_played(-1, 'Ampache', array());
@@ -493,4 +534,12 @@ class Channel extends database_object implements media, library_item
}
+ private function strtohex($x)
+ {
+ $s='';
+ foreach(str_split($x) as $c) $s.=sprintf("%02X",ord($c));
+ return($s);
+ }
+
+
} // end of channel class