650fea | 2001-04-10 | Per Hedbor | | inherit "module";
|
6877ce | 2001-04-11 | Per Hedbor | | constant cvs_version="$Id: icecast.pike,v 1.5 2001/04/11 08:45:49 per Exp $";
|
650fea | 2001-04-10 | Per Hedbor | | constant thread_safe=1;
|
b779eb | 2001-04-10 | Per Hedbor | | #define BSIZE 8192
#define METAINTERVAL 4192
|
650fea | 2001-04-10 | Per Hedbor | | #include <module.h>
#include <roxen.h>
#include <stat.h>
#include <request_trace.h>
#define _(X,Y) _DEF_LOCALE("mod_icecast",X,Y)
constant module_type = MODULE_LOCATION;
LocaleString module_name = _(0,"Icecast: Server");
|
4f76ba | 2001-04-10 | Per Hedbor | | LocaleString module_doc = _(0, "Supports the ICY and Audio-cast protocols "
"for streaming MPEG sound. Relies on other "
"modules for the actual mpeg streams." );
|
650fea | 2001-04-10 | Per Hedbor | | constant module_unique = 0;
class Playlist
{
inherit RoxenModule;
Stdio.File next_file();
string pl_name();
mapping metadata();
void add_md_callback( function(mapping:void) f );
void remove_md_callback( function(mapping:void) f );
};
class MPEGStream( Playlist playlist )
{
array(function) callbacks = ({});
int bitrate;
int stream_position;
int stream_start = time()*1000+(int)(time(time())*1000);
Stdio.File fd;
|
b779eb | 2001-04-10 | Per Hedbor | |
string status()
{
return ""+playlist->status()+"<br />"+
sprintf( "Uptime: %.1fs Bitrate: %dKbit/sec",
stream_position/1000.0,
bitrate/1000 );
}
|
650fea | 2001-04-10 | Per Hedbor | |
void add_callback( function callback )
{
callbacks += ({ callback });
}
void remove_callback( function callback )
{
callbacks -= ({ callback });
}
void call_callbacks( mixed ... args )
{
foreach( callbacks, function f )
|
b779eb | 2001-04-10 | Per Hedbor | | if( mixed e = catch( f(@args) ) )
{
werror(describe_backtrace( e ) );
|
6877ce | 2001-04-11 | Per Hedbor | | remove_callback( f );
|
b779eb | 2001-04-10 | Per Hedbor | | }
|
650fea | 2001-04-10 | Per Hedbor | | }
int realtime()
{
return (time()*1000+(int)(time(time())*1000)) - stream_start;
}
|
b779eb | 2001-04-10 | Per Hedbor | | void destroy()
{
catch(fd->set_blocking());
catch(fd->close());
}
|
650fea | 2001-04-10 | Per Hedbor | | void feeder_thread( )
{
|
6877ce | 2001-04-11 | Per Hedbor | | while( sizeof(callbacks) &&
realtime() > stream_position )
|
650fea | 2001-04-10 | Per Hedbor | | {
string frame = get_frame();
while( !frame )
{
|
b779eb | 2001-04-10 | Per Hedbor | | fd->set_blocking();
|
650fea | 2001-04-10 | Per Hedbor | | fd->close();
fd = playlist->next_file();
|
b779eb | 2001-04-10 | Per Hedbor | | buffer = 0;
|
650fea | 2001-04-10 | Per Hedbor | | bpos = 0;
frame = get_frame();
}
|
6877ce | 2001-04-11 | Per Hedbor | |
|
650fea | 2001-04-10 | Per Hedbor | | stream_position += strlen(frame)*8000 / bitrate;
|
6877ce | 2001-04-11 | Per Hedbor | | werror( "%d\n", strlen(frame)*8000 / bitrate );
|
650fea | 2001-04-10 | Per Hedbor | | call_callbacks( frame );
|
6877ce | 2001-04-11 | Per Hedbor | | }
if(!sizeof(callbacks))
{
stream_position = 0;
stream_start = (time()*1000+(int)(time(time())*1000));
}
|
650fea | 2001-04-10 | Per Hedbor | | call_out( feeder_thread, 0.02 );
}
void start()
{
feeder_thread( );
}
static string buffer;
static int bpos;
static string|int getbytes( int n, int|void s )
{
if( !fd )
fd = playlist->next_file();
|
6877ce | 2001-04-11 | Per Hedbor | | if( !fd )
return s?0:-1;
|
b779eb | 2001-04-10 | Per Hedbor | | if( !buffer || !strlen(buffer) )
{
bpos = 0;
|
650fea | 2001-04-10 | Per Hedbor | | buffer = fd->read( BSIZE );
|
b779eb | 2001-04-10 | Per Hedbor | | }
if( !strlen(buffer) )
return s?0:-1;
|
650fea | 2001-04-10 | Per Hedbor | | if( s )
{
if( strlen(buffer) - bpos > n )
{
string d = buffer[bpos..bpos+n-1];
buffer = buffer[bpos+n..];
bpos=0;
return d;
}
else
{
buffer = buffer[bpos..];
bpos=0;
string t = fd->read( BSIZE );
if( !t || !strlen(t) )
return -1;
buffer+=t;
return getbytes(n,1);
}
}
int res=0;
while( n-- )
{
res<<=8;
res|=buffer[ bpos++ ];
if( bpos == strlen(buffer) )
{
bpos = 0;
buffer = fd->read( BSIZE );
if( !buffer || !strlen( buffer ) )
return -1;
}
}
return res;
}
static int rate_of(int r)
{
switch(r)
{
case 0: return 44100;
case 1: return 48000;
case 2: return 32000;
default:return 44100;
}
}
static array(array(int)) bitrates =
({
({0,32,64,96,128,160,192,224,256,288,320,352,384,416,448}),
({0,32,48,56,64,80,96,112,128,160,192,224,256,320,384}),
({0,32,40,48,56,64,80,96,112,128,160,192,224,256,320}),
});
static string get_frame()
{
string data;
int trate = 0;
int patt = 0;
int by, p=0, sw=0;
while( (by = getbytes( 1 )) > 0 )
{
patt <<= 8;
patt |= by;
p++;
if( (patt & 0xfff0) == 0xfff0 )
{
int srate, channels, layer, ID, pad, blen;
int header = ((patt&0xffff)<<16);
if( (by = getbytes( 2 )) < 0 )
break;
header |= by;
string data = sprintf("%4c",header);
patt=0;
header <<= 12;
int getbits( int n )
{
int res = 0;
while( n-- )
{
res <<= 1;
if( header&(1<<31) ) res |= 1;
header<<=1;
}
return res;
};
ID = getbits(1);
if(!ID)
continue;
layer = (4-getbits(2));
header<<=1;
bitrate = getbits(4);
srate = getbits(2);
if((layer>3) || (layer<2) || (bitrate>14) || (srate>2))
continue;
pad = getbits(1);
bitrate = bitrates[ layer-1 ][ bitrate ] * 1000;
srate = rate_of( srate );
switch( layer )
{
case 1:
blen = (int)(12 * bitrate / (float)srate + (pad?4:0))-4;
break;
case 2:
case 3:
blen = (int)(144 * bitrate / (float)srate + pad )-4;
break;
}
|
b779eb | 2001-04-10 | Per Hedbor | | string q = getbytes( blen,1 );
if(!q)
return 0;
return data + q;
|
650fea | 2001-04-10 | Per Hedbor | | }
}
return 0;
}
}
class Location( string location,
string initial,
MPEGStream stream,
int max_connections )
{
int accessed, denied;
int connections;
|
b779eb | 2001-04-10 | Per Hedbor | | int total_time, max_time, successful;
|
650fea | 2001-04-10 | Per Hedbor | | array(Connection) conn = ({});
mapping handle( RequestID id )
{
|
b779eb | 2001-04-10 | Per Hedbor | | NOCACHE();
|
650fea | 2001-04-10 | Per Hedbor | | accessed++;
if( connections == max_connections )
{
denied++;
return Roxen.http_string_answer( "Too many listeners\n" );
}
|
6877ce | 2001-04-11 | Per Hedbor | | if( !stream->fd )
stream->fd = stream->playlist->next_file();
|
b779eb | 2001-04-10 | Per Hedbor | | mapping meta = stream->playlist->metadata();
if( !meta )
{
denied++;
return Roxen.http_string_answer( "Too early\n" );
}
connections++;
int use_metadata;
string i, metahd="";
|
9c812a | 2001-04-10 | Per Hedbor | | string protocol = "ICY";
|
650fea | 2001-04-10 | Per Hedbor | |
|
b779eb | 2001-04-10 | Per Hedbor | | werror("%O\n", id->request_headers );
if( id->request_headers[ "icy-metadata" ] )
{
}
if( id->request_headers[ "x-audiocast-udpport" ] )
{
protocol = "AudioCast";
i = ("HTTP/1.0 200 OK\r\n"
"Server: "+roxen.version()+"\r\n"
"Content-type: audio/mpeg\r\n"
"x-audiocast-name:"+(meta->name||meta->path)+"\r\n"
"x-audiocast-gengre:"+(meta->gengre||"unknown")+"\r\n"
"x-audiocast-url:"+("http://"+id->misc->host+
query_location()+location)+"\r\n"+
"x-audiocast-streamid:1\r\n"+metahd+
"x-audiocast-public:1\r\n"
"x-audiocast-bitrate:"+(stream->bitrate/1000)+"\r\n"
"x-audiocast-description:Served by Roxen\r\n"
"\r\n" );
}
else
{
i = ("ICY 200 OK\r\n"
"Server: "+roxen.version()+"\r\n"
"Content-type: audio/mpeg\r\n"
"icy-notice1:This stream requires a shoutcast compatible player.\r\n"
"icy-notice2:Roxen mod_mp3\r\n"+metahd+
"icy-name:"+(meta->name||meta->path)+"\r\n"
"icy-gengre:"+(meta->gengre||"unknown")+"\r\n"
"icy-url:"+("http://"+id->misc->host+
query_location()+location)+"\r\n"+
"icy-pub:1\r\n"
"icy-br:"+(stream->bitrate/1000)+"\r\n"
"\r\n" );
}
|
650fea | 2001-04-10 | Per Hedbor | | if( initial )
i += initial;
|
b779eb | 2001-04-10 | Per Hedbor | | conn += ({ Connection( id->my_fd, i,protocol,use_metadata,
|
650fea | 2001-04-10 | Per Hedbor | | stream,
lambda( Connection c ){
|
b779eb | 2001-04-10 | Per Hedbor | | int pt = time()-c->connected;
|
650fea | 2001-04-10 | Per Hedbor | | conn-=({c});
|
b779eb | 2001-04-10 | Per Hedbor | | total_time += pt;
if( pt > max_time )
max_time = pt;
successful++;
|
650fea | 2001-04-10 | Per Hedbor | | connections--;
} ) });
return Roxen.http_pipe_in_progress( );
}
|
b779eb | 2001-04-10 | Per Hedbor | |
string format_time( int t )
{
if( t > 60*60 )
return sprintf( "%d:%02d:%02d", t/3600, (t/60)%60, t%60 );
return sprintf( "%2d:%02d", (t/60)%60, t%60 );
}
string avg_listen_time()
{
int tt = total_time;
int n = successful;
foreach( conn, Connection c )
{
n++;
tt += time()-c->connected;
}
int t = tt / (n||1);
return format_time( t );
}
string longest_listen_time()
{
foreach( conn, Connection c )
if( time()-c->connected > max_time )
max_time = time()-c->connected;
return format_time( max_time );
}
string status()
{
string res = "<table>";
res += "<tr> <td colspan=2>"+
""+stream->status()+"</td></tr>"
"<tr><td>Connections:</td><td>"+connections+"/"+max_connections+" ("+
accessed+" since server start, "+denied+" denied)</td></tr><tr>"
"<tr><td>Average listen time:</td><td>"+avg_listen_time()+"</td></tr>"
"<tr><td>Longest listen time:</td><td>"+longest_listen_time()+"</td></tr>"
"<tr><td>Current connections:</td><td>";
foreach( conn, Connection c )
res += " "+c->status()+"\n";
return res+"</td></tr></table>";
}
|
650fea | 2001-04-10 | Per Hedbor | | }
mapping(string:Location) locations = ([]);
|
b779eb | 2001-04-10 | Per Hedbor | | int __id=1;
|
650fea | 2001-04-10 | Per Hedbor | | class Connection
{
Stdio.File fd;
MPEGStream stream;
|
b779eb | 2001-04-10 | Per Hedbor | | int do_meta, last_meta;
string protocol;
|
650fea | 2001-04-10 | Per Hedbor | | int sent, skipped;
|
b779eb | 2001-04-10 | Per Hedbor | | int sent_bytes;
int id = __id++;
|
650fea | 2001-04-10 | Per Hedbor | | array buffer = ({});
string current_block;
function _ccb;
|
b779eb | 2001-04-10 | Per Hedbor | | mapping current_md;
int connected = time();
string status()
{
return sprintf( "%d. %s Time: %ds Remote: %s "
"%d sent, %d skipped<br />",id,protocol,
time()-connected,
(fd->query_address()/" ")[0],
|
6877ce | 2001-04-11 | Per Hedbor | | sent, skipped );
|
b779eb | 2001-04-10 | Per Hedbor | | }
string gen_metadata( )
{
if(!current_md )
current_md = stream->playlist->metadata();
string s = sprintf( " StreamTitle='%s';StreamUrl='%s';",
current_md->name || current_md->path,
"")+"\0";
while( strlen(s) & 15 ) s+= "\0";
s[0]=strlen(s);
return s;
}
|
650fea | 2001-04-10 | Per Hedbor | |
static void callback( string frame )
{
buffer += ({ frame });
if( sizeof( buffer ) > 10 )
{
skipped++;
buffer = buffer[1..];
}
|
6877ce | 2001-04-11 | Per Hedbor | | if( sizeof( buffer ) == 1 )
|
9c812a | 2001-04-10 | Per Hedbor | | send_more();
|
650fea | 2001-04-10 | Per Hedbor | | }
|
b779eb | 2001-04-10 | Per Hedbor | | int headers_done;
|
650fea | 2001-04-10 | Per Hedbor | | static void send_more()
{
if( !strlen(current_block) )
{
|
b779eb | 2001-04-10 | Per Hedbor | | headers_done = 1;
|
650fea | 2001-04-10 | Per Hedbor | | if( !sizeof(buffer) )
|
b779eb | 2001-04-10 | Per Hedbor | | {
|
650fea | 2001-04-10 | Per Hedbor | | return;
|
b779eb | 2001-04-10 | Per Hedbor | | }
|
650fea | 2001-04-10 | Per Hedbor | | current_block = buffer[0];
|
b779eb | 2001-04-10 | Per Hedbor | |
if( do_meta )
{
if( (strlen( current_block ) + sent_bytes) - last_meta >= METAINTERVAL )
{
last_meta = (strlen( current_block ) + sent_bytes);
current_block += gen_metadata();
}
}
|
650fea | 2001-04-10 | Per Hedbor | | buffer = buffer[1..];
sent++;
}
int n = fd->write( current_block );
|
6877ce | 2001-04-11 | Per Hedbor | |
if( n > 0 )
{
if( headers_done )
sent_bytes += n;
current_block = current_block[n..];
}
|
650fea | 2001-04-10 | Per Hedbor | | }
|
b779eb | 2001-04-10 | Per Hedbor | | static void md_callback( mapping metadata )
{
current_md = metadata;
}
|
650fea | 2001-04-10 | Per Hedbor | | static void closed( )
{
|
6877ce | 2001-04-11 | Per Hedbor | | fd->set_blocking( );
|
650fea | 2001-04-10 | Per Hedbor | | fd = 0;
stream->remove_callback( callback );
|
b779eb | 2001-04-10 | Per Hedbor | | stream->playlist->remove_md_callback( md_callback );
|
650fea | 2001-04-10 | Per Hedbor | | _ccb( this_object() );
|
6877ce | 2001-04-11 | Per Hedbor | | werror("Closed from client side\n");
|
650fea | 2001-04-10 | Per Hedbor | | }
|
b779eb | 2001-04-10 | Per Hedbor | | static void create( Stdio.File _fd, string buffer,
string prot, int _meta, MPEGStream _stream,
|
650fea | 2001-04-10 | Per Hedbor | | function _closed )
{
|
b779eb | 2001-04-10 | Per Hedbor | | protocol = prot;
|
650fea | 2001-04-10 | Per Hedbor | | fd = _fd;
|
b779eb | 2001-04-10 | Per Hedbor | | do_meta = _meta;
|
650fea | 2001-04-10 | Per Hedbor | | stream = _stream;
_ccb = _closed;
current_block = buffer;
|
6877ce | 2001-04-11 | Per Hedbor | | fd->set_nonblocking( 0, send_more, closed );
|
b779eb | 2001-04-10 | Per Hedbor | | if( stream )
stream->add_callback( callback );
if( stream->playlist )
stream->playlist->add_md_callback( md_callback );
|
650fea | 2001-04-10 | Per Hedbor | | }
}
class VarStreams
{
inherit Variable.List;
constant type="StreamList";
string render_row( string prefix, mixed val, int width )
{
string res = "<input type=hidden name='"+prefix+"' value='"+prefix+"' />";
array split = val/"\0";
res += (query_location()+"<input name='"+prefix+"loc' value='"+
Roxen.http_encode_string(split[0])+"' size=20/>");
res += "<select name='"+prefix+"playlist'>";
mapping pl;
foreach( sort(indices(pl=playlists(0))), string p )
if( pl[ p ]->sname() == split[1] )
res += "<option value='"+pl[p]->sname()+"' selected='t'>"+p+"</option>";
else
res += "<option value='"+pl[p]->sname()+"'>"+p+"</option>";
res += "</select><br />";
res += ("conn: <input name='"+prefix+"conn' value='"+
((int)split[3]||10)+"' size=5/>");
res += ("jingle: <input name='"+prefix+"jingle' value='"+
Roxen.http_encode_string(split[2])+"' size=40/>");
return res;
}
string transform_from_form( string v, mapping va )
{
if( v == "" ) return "\0\0\0\0";
v = v[strlen(path())..];
return va[v+"loc"]+"\0"+va[v+"playlist"]+"\0"+va[v+"jingle"]
+"\0"+va[v+"conn"];
}
array(mapping) get_streams()
{
array res = ({});
foreach( query(), string s )
{
mapping m = ([]);
array a = s/"\0";
m->location = a[0];
m->playlist = a[1];
m->jingle = a[2];
m->maxconn = (int)a[3];
if( !strlen( m->jingle ) )
m_delete( m, "jingle" );
res += ({ m });
}
return res;
}
}
void create()
{
defvar( "streams", VarStreams( ({}), 0, _(0,"Streams"),
_(0,"All the streams") ) );
defvar("location", "/strm/",
_(0,"Mount point"), TYPE_LOCATION|VAR_INITIAL,
_(0,"Where the module will be mounted in the site's virtual "
"file system."));
}
mapping playlists(int s)
{
mapping res = ([ ]);
foreach( my_configuration()->get_providers( "icecast:playlist" ),
Playlist m )
{
if( s )
res[ m->sname() ] = m;
else
res[ m->pl_name() ] = m;
}
return res;
}
mapping streams = ([ ]);
|
b779eb | 2001-04-10 | Per Hedbor | | void st2()
|
650fea | 2001-04-10 | Per Hedbor | | {
mapping pl = playlists( 1 );
foreach( getvar( "streams" )->get_streams(), mapping strm )
{
MPEGStream mps;
|
b779eb | 2001-04-10 | Per Hedbor | | if( strm->playlist && pl[ strm->playlist ] )
|
650fea | 2001-04-10 | Per Hedbor | | if( !(mps = streams[ pl[strm->playlist] ]) )
{
mps = streams[pl[strm->playlist]] = MPEGStream( pl[strm->playlist] );
mps->start();
}
|
b779eb | 2001-04-10 | Per Hedbor | | if( !mps )
continue;
|
650fea | 2001-04-10 | Per Hedbor | | if( !locations[ strm->location ] )
locations[ strm->location ] =
Location( strm->location,
(strm->jingle?
Stdio.read_file( strm->jingle ) :
0 ),
mps,
(int)strm->maxconn );
}
}
|
b779eb | 2001-04-10 | Per Hedbor | | void start()
{
call_out( st2, 0.5 );
}
|
650fea | 2001-04-10 | Per Hedbor | | mapping find_file( string f, RequestID id )
{
if( locations[f] )
return locations[f]->handle( id );
return 0;
}
|
b779eb | 2001-04-10 | Per Hedbor | |
string status()
{
string res = "<table>";
foreach( indices( locations ), string loc )
{
res += "<tr><td valign=top>"+loc+"</td><td valign=top>"+
locations[loc]->status()+"</td></tr>";
}
return res+"</table>";
}
|