d69ba52001-09-03Martin Nilsson // This is a roxen module. Copyright © 2001, Roxen IS.
650fea2001-04-10Per Hedbor inherit "module";
f9fc992002-06-12Honza Petrous constant cvs_version="$Id: icecast.pike,v 1.10 2002/06/12 05:06:30 hop Exp $";
650fea2001-04-10Per Hedbor constant thread_safe=1;
f9fc992002-06-12Honza Petrous #define BSIZE 16384 #define METAINTERVAL 8192
b779eb2001-04-10Per Hedbor 
650fea2001-04-10Per Hedbor #include <module.h> #include <roxen.h> #include <stat.h> #include <request_trace.h> //<locale-token project="mod_icecast">LOCALE</locale-token> #define _(X,Y) _DEF_LOCALE("mod_icecast",X,Y)
f9fc992002-06-12Honza Petrous constant module_type = MODULE_LOCATION | MODULE_TAG;
650fea2001-04-10Per Hedbor LocaleString module_name = _(0,"Icecast: Server");
4f76ba2001-04-10Per 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." );
650fea2001-04-10Per Hedbor constant module_unique = 0;
f9fc992002-06-12Honza Petrous TAGDOCUMENTATION; #ifdef manual constant tagdoc=([ "emit#mp3-stream": ({ #"<desc type='plugin'><p><short> Use this source to retrieve information about currently configured MP3 streams.</short></p> <p>Playlist for given stream can be reached by RXML or by http query with '?playlist' or '?playlist?=maxlines', where maxlines is maximum number of returned items.</p> </desc> <attr name='name' value='name of stream'><p> Name of configured stream.</p> </attr>", ([ "&_.name;":#"<desc type='name'><p> The name of the stream. </p></desc>", "&_.current-song;":#"<desc type='current-song'><p> The filename of current streamed song. </p></desc>", "&_.next-song;":#"<desc type='next-song'><p> The filename of next streamed song. </p></desc>", "&_.real-dir;":#"<desc type='real-dir'><p> The name base directory from real filesystem. </p></desc>", "&_.accessed;":#"<desc type='accessed'><p> The number of accessed connections. </p></desc>", "&_.denied;":#"<desc type='denied'><p> The number of denied connections. </p></desc>", "&_.connections;":#"<desc type='connections'><p> The number of current connections. </p></desc>", "&_.avg-listen-time;":#"<desc type='avg-listen-time'><p> The avarage of listen time. </p></desc>", "&_.max-listen-time;":#"<desc type='max-listen-time'><p> The maximum of listen time. </p></desc>", #if 0 "&_.con;":#"<desc type='con'><p> Array of current connections. </p></desc>" "&_.playlist;":#"<desc type='playlist'><p> Playlist arranged in array of songs. </p></desc>" #endif ]) }) ]); #endif
650fea2001-04-10Per Hedbor 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 );
f9fc992002-06-12Honza Petrous  #if 0 mixed `->(string index) { #endif
650fea2001-04-10Per Hedbor };
f9fc992002-06-12Honza Petrous class MPEGStream
650fea2001-04-10Per Hedbor { array(function) callbacks = ({});
c0c9b92001-04-11Per Hedbor  int bitrate; // bits/second int stream_start=realtime(), stream_position; // µSeconds
650fea2001-04-10Per Hedbor  Stdio.File fd;
f9fc992002-06-12Honza Petrous #if constant(Parser.MP3) Parser.MP3.File parser; #else Audio.MP3.decode parser; #endif Playlist playlist; string cmd; mixed get_playlist() { if(!playlist || zero_type(playlist["current_file"])) { // playlist object is lost werror("DEB: playlist %O is lost!\n", "???"); return 0; } return playlist; } void create(Playlist plist) { playlist = plist; fd = playlist->next_file(); }
b779eb2001-04-10Per Hedbor  string status() {
f9fc992002-06-12Honza Petrous  return ""+get_playlist()->status()+"<br />"+
c0c9b92001-04-11Per Hedbor  sprintf( "Stream position: %.1fs Bitrate: %dKbit/sec", stream_position/1000000.0,
b779eb2001-04-10Per Hedbor  bitrate/1000 ); }
c0c9b92001-04-11Per Hedbor 
650fea2001-04-10Per Hedbor  void add_callback( function callback ) { callbacks += ({ callback }); } void remove_callback( function callback ) { callbacks -= ({ callback }); } void call_callbacks( mixed ... args ) { foreach( callbacks, function f )
b779eb2001-04-10Per Hedbor  if( mixed e = catch( f(@args) ) ) { werror(describe_backtrace( e ) );
6877ce2001-04-11Per Hedbor  remove_callback( f );
b779eb2001-04-10Per Hedbor  }
650fea2001-04-10Per Hedbor  }
c0c9b92001-04-11Per Hedbor  static int last_hrtime, base_time;
650fea2001-04-10Per Hedbor  int realtime() {
c0c9b92001-04-11Per Hedbor  if(!last_hrtime) { last_hrtime = gethrtime(); base_time = 0; } else { int nt = gethrtime(); base_time += nt-last_hrtime; last_hrtime = nt; } return base_time;
650fea2001-04-10Per Hedbor  }
b779eb2001-04-10Per Hedbor  void destroy() { catch(fd->set_blocking()); catch(fd->close()); }
650fea2001-04-10Per Hedbor  void feeder_thread( ) {
f9fc992002-06-12Honza Petrous  if(cmd) { switch(cmd) { case "forward": if(fd = get_playlist()->cmd_next_file()) { stream_position = 0; parser = 0; } break; case "back": if(fd = get_playlist()->cmd_prev_file()) { stream_position = 0; parser = 0; } break; } cmd = 0; }
6877ce2001-04-11Per Hedbor  while( sizeof(callbacks) &&
c0c9b92001-04-11Per Hedbor  ((realtime()-stream_start) > stream_position) )
650fea2001-04-10Per Hedbor  {
f9fc992002-06-12Honza Petrous  mapping frame; if(!parser) #if constant(Parser.MP3) parser = Parser.MP3.File(fd); #else parser = Audio.MP3.decode(fd); #endif frame = parser->get_frame();
650fea2001-04-10Per Hedbor  while( !frame ) {
b779eb2001-04-10Per Hedbor  fd->set_blocking();
650fea2001-04-10Per Hedbor  fd->close();
f9fc992002-06-12Honza Petrous  fd = get_playlist()->next_file(); #if constant(Parser.MP3) parser = Parser.MP3.File(fd); #else parser = Audio.MP3.decode(fd); #endif frame = parser->get_frame();
650fea2001-04-10Per Hedbor  }
c0c9b92001-04-11Per Hedbor  // Actually, this is supposed to be quite constant, but not all // frames are the same size.
f9fc992002-06-12Honza Petrous  stream_position += strlen(frame->data)*8000000 / frame->bitrate; call_callbacks( frame->data );
6877ce2001-04-11Per Hedbor  } if(!sizeof(callbacks)) { stream_position = 0;
c0c9b92001-04-11Per Hedbor  stream_start = realtime();
6877ce2001-04-11Per Hedbor  }
650fea2001-04-10Per Hedbor  call_out( feeder_thread, 0.02 ); } void start() { feeder_thread( ); // not actually a thread right now. } } class Location( string location, string initial, MPEGStream stream,
c0c9b92001-04-11Per Hedbor  int max_connections, string url, string name )
650fea2001-04-10Per Hedbor  { int accessed, denied; int connections;
b779eb2001-04-10Per Hedbor  int total_time, max_time, successful;
650fea2001-04-10Per Hedbor  array(Connection) conn = ({}); mapping handle( RequestID id ) {
b779eb2001-04-10Per Hedbor  NOCACHE();
650fea2001-04-10Per Hedbor  accessed++; if( connections == max_connections ) { denied++; return Roxen.http_string_answer( "Too many listeners\n" ); }
6877ce2001-04-11Per Hedbor  if( !stream->fd )
f9fc992002-06-12Honza Petrous  stream->fd = stream->get_playlist()->next_file();
6877ce2001-04-11Per Hedbor 
f9fc992002-06-12Honza Petrous  mapping meta = stream->get_playlist()->metadata();
b779eb2001-04-10Per Hedbor  if( !meta ) { denied++; return Roxen.http_string_answer( "Too early\n" ); } connections++; int use_metadata; string i, metahd="";
9c812a2001-04-10Per Hedbor  string protocol = "ICY";
f9fc992002-06-12Honza Petrous  int client_udp;
650fea2001-04-10Per Hedbor 
f9fc992002-06-12Honza Petrous  werror("Client: %O\n", id->request_headers ); if(id->request_headers[ "icy-metadata" ] )
b779eb2001-04-10Per Hedbor  {
d3cea92001-05-16Per Hedbor  use_metadata = (int)id->request_headers[ "icy-metadata" ]; if( use_metadata ) metahd = "icy-metaint:"+METAINTERVAL+"\r\n";
b779eb2001-04-10Per Hedbor  }
f9fc992002-06-12Honza Petrous 
b779eb2001-04-10Per Hedbor  if( id->request_headers[ "x-audiocast-udpport" ] ) { protocol = "AudioCast";
f9fc992002-06-12Honza Petrous  if(id->request_headers[ "icy-metadata" ] && query("udpmeta") ) { metahd = "x-audiocast-udpport: " + query("udpmeta") + "\r\n"; client_udp = (int)id->request_headers[ "x-audiocast-udpport" ] ; use_metadata = 0; }
b779eb2001-04-10Per Hedbor  i = ("HTTP/1.0 200 OK\r\n" "Server: "+roxen.version()+"\r\n" "Content-type: audio/mpeg\r\n" "x-audiocast-gengre:"+(meta->gengre||"unknown")+"\r\n"
c0c9b92001-04-11Per Hedbor  +((meta->url||url)?"x-audiocast-url:"+(meta->url||url)+"\r\n":"")+
f9fc992002-06-12Honza Petrous  "x-audiocast-name:"+name+"\r\n"
b779eb2001-04-10Per Hedbor  "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 {
f9fc992002-06-12Honza Petrous  if( id->request_headers[ "icy-metadata" ] ) i = ("ICY 200 OK\r\n"
b779eb2001-04-10Per Hedbor  "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+
f9fc992002-06-12Honza Petrous  "icy-name:"+name+"\r\n"
b779eb2001-04-10Per Hedbor  "icy-gengre:"+(meta->gengre||"unknown")+"\r\n"
c0c9b92001-04-11Per Hedbor  +((meta->url||url)?"icy-url:"+(meta->url||url)+"\r\n":"")+
b779eb2001-04-10Per Hedbor  "icy-pub:1\r\n" "icy-br:"+(stream->bitrate/1000)+"\r\n" "\r\n" );
f9fc992002-06-12Honza Petrous  else { werror("MS Player?\n"); protocol = "AudioCast"; i = ("HTTP/1.0 200 OK\r\n" "Server: "+roxen.version()+"\r\n" "Content-type: audio/mpeg\r\n" "Content-Length: 9999999999\r\n" ); }
b779eb2001-04-10Per Hedbor  }
650fea2001-04-10Per Hedbor  if( initial ) i += initial;
b779eb2001-04-10Per Hedbor  conn += ({ Connection( id->my_fd, i,protocol,use_metadata,
d3cea92001-05-16Per Hedbor  stream, this_object(),
650fea2001-04-10Per Hedbor  lambda( Connection c ){
b779eb2001-04-10Per Hedbor  int pt = time()-c->connected;
8626872001-04-17Per Hedbor  conn -= ({ c });
b779eb2001-04-10Per Hedbor  total_time += pt; if( pt > max_time ) max_time = pt; successful++;
650fea2001-04-10Per Hedbor  connections--;
f9fc992002-06-12Honza Petrous  }, client_udp ) });
650fea2001-04-10Per Hedbor  return Roxen.http_pipe_in_progress( ); }
b779eb2001-04-10Per 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 )
8626872001-04-17Per Hedbor  catch { res += " "+c->status()+"\n"; };
b779eb2001-04-10Per Hedbor  return res+"</td></tr></table>"; }
650fea2001-04-10Per Hedbor } mapping(string:Location) locations = ([]);
b779eb2001-04-10Per Hedbor int __id=1;
f9fc992002-06-12Honza Petrous Stdio.UDP udpstream; int q_playlist;
650fea2001-04-10Per Hedbor class Connection { Stdio.File fd; MPEGStream stream;
d3cea92001-05-16Per Hedbor  Location location; int do_meta, meta_cnt;
b779eb2001-04-10Per Hedbor  string protocol;
650fea2001-04-10Per Hedbor  int sent, skipped; // frames.
d3cea92001-05-16Per Hedbor  int sent_bytes, sent_meta;
b779eb2001-04-10Per Hedbor  int id = __id++;
650fea2001-04-10Per Hedbor  array buffer = ({}); string current_block; function _ccb;
b779eb2001-04-10Per Hedbor  mapping current_md;
f9fc992002-06-12Honza Petrous  int cudp; string claddr;
b779eb2001-04-10Per Hedbor  int connected = time(); string status() {
8626872001-04-17Per Hedbor  if(!fd) return "Closed stream\n";
b779eb2001-04-10Per Hedbor  return sprintf( "%d. %s Time: %ds Remote: %s "
f9fc992002-06-12Honza Petrous  "%d sent, %d skipped, meta: %s<br />",id,protocol,
b779eb2001-04-10Per Hedbor  time()-connected,
f9fc992002-06-12Honza Petrous  claddr, sent, skipped, cudp ? "UDP" : (do_meta ? "inline" : ""));
b779eb2001-04-10Per Hedbor  }
d3cea92001-05-16Per Hedbor  string old_mdkey;
b779eb2001-04-10Per Hedbor  string gen_metadata( ) {
f9fc992002-06-12Honza Petrous  string s = "";
d3cea92001-05-16Per Hedbor  if( !current_md )
f9fc992002-06-12Honza Petrous  current_md = stream->get_playlist()->metadata();
d3cea92001-05-16Per Hedbor  if( (current_md->name||current_md->path)+current_md->url != old_mdkey ) { old_mdkey = (current_md->name||current_md->path)+current_md->url;
f9fc992002-06-12Honza Petrous  s = sprintf( "StreamTitle='%s';StreamUrl='%s';", get_streamtitle(), location->url || current_md->url );
d3cea92001-05-16Per Hedbor  } while( strlen(s) & 15 ) s+= "\0";
f9fc992002-06-12Honza Petrous  s = " " + s; s[ 0 ]=(strlen(s)-1)/16; //if(s[0]) // werror("MD: %O\n", s );
b779eb2001-04-10Per Hedbor  return s; }
f9fc992002-06-12Honza Petrous  void send_udptitle() { if(!current_md) { werror("No metadata found.\n"); return; } //udp->send("192.168.1.3", 10003, "x-audiocast-udpseqnr: 1\r\nx-audiocast-streamtitle: hop - xxx\r\nx-audiocast-streamurl: http://mepege.unibase.cz/strm/en\r\nx-audiocast-streamleght: 3222111"); udpstream->send(claddr, cudp, "x-audiocast-streamtitle: "+get_streamtitle()+"\r\n"); }
650fea2001-04-10Per Hedbor  static void callback( string frame ) { buffer += ({ frame });
c0c9b92001-04-11Per Hedbor  if( sizeof( buffer ) > 100 )
650fea2001-04-10Per Hedbor  { skipped++; buffer = buffer[1..]; }
6877ce2001-04-11Per Hedbor  if( sizeof( buffer ) == 1 )
c0c9b92001-04-11Per Hedbor  { remove_call_out( send_more ); call_out( send_more, 0.01 ); }
650fea2001-04-10Per Hedbor  }
b779eb2001-04-10Per Hedbor  int headers_done;
f9fc992002-06-12Honza Petrous  /* http://www.xiph.org/archives/icecast-dev/0060.html There are updates every interval. Please notice the 'justnull' variable. Apparently you haven't fully understood the format of the metadata: the first byte of metadata is a length byte. Multiply it by 16 to determine how much text to extract from the stream as metadata. If the first byte is null, then you extract 0 bytes and continue to use the metadata you already have. Every 4096 bytes you either get new metadata if the udpseqnr doesn't match between source and client, or you get a 'null' length byte. You can't just omit the data without causing skipping problems - valid mp3 data would be interpreted as a length byte and that amount of data would be removed from the stream. */ /* http://www.radiotoolbox.com/forums/viewtopic.php?t=76 I tried hosting PLS files with my web host and it didn't work, the files just got displayed as text files, why did this happen? Simply because your web servers MIME types were not setup properly to handle PLS files, in this case you must add them to your MIME types file. The proper entry is: Code: audio/x-scpls pls or if you are using M3U files Code: audio/x-mpegurl m3u If your web host does not allow you to access your mime types, you can request that it be added or, if your web host supports it, you can use the .htaccess file to add the mime type to the server. */
b779eb2001-04-10Per Hedbor 
650fea2001-04-10Per Hedbor  static void send_more() { if( !strlen(current_block) ) {
b779eb2001-04-10Per Hedbor  headers_done = 1;
650fea2001-04-10Per Hedbor  if( !sizeof(buffer) ) return; current_block = buffer[0];
b779eb2001-04-10Per Hedbor  if( do_meta ) {
d3cea92001-05-16Per Hedbor  meta_cnt += strlen( current_block ); if( meta_cnt >= METAINTERVAL )
b779eb2001-04-10Per Hedbor  {
f9fc992002-06-12Honza Petrous  string meta = gen_metadata();
d3cea92001-05-16Per Hedbor  meta_cnt -= METAINTERVAL; int rest = strlen(current_block)-meta_cnt-1;
f9fc992002-06-12Honza Petrous  sent_meta += strlen(meta); current_block = current_block[..rest]+meta+current_block[rest+1..];
b779eb2001-04-10Per Hedbor  } }
650fea2001-04-10Per Hedbor  buffer = buffer[1..]; sent++; }
f9fc992002-06-12Honza Petrous  int n = -1; catch( n = fd->write( current_block ));
6877ce2001-04-11Per Hedbor  if( n > 0 ) { if( headers_done ) sent_bytes += n; current_block = current_block[n..]; }
8626872001-04-17Per Hedbor  else if( n < 0 ) closed();
650fea2001-04-10Per Hedbor  }
f9fc992002-06-12Honza Petrous  string get_streamtitle() { string title = query("mdtitle"); title = replace(title, "%title%", current_md->title||""); title = replace(title, "%artist%", current_md->artist||""); title = replace(title, "%album%", current_md->album||""); title = replace(title, "%track%", current_md->track||""); title = replace(title, "%year%", (""+current_md->year) == "0" ? "" : current_md->year ); return title; }
b779eb2001-04-10Per Hedbor  static void md_callback( mapping metadata ) { current_md = metadata;
f9fc992002-06-12Honza Petrous  if(cudp && udpstream) send_udptitle();
b779eb2001-04-10Per Hedbor  }
650fea2001-04-10Per Hedbor  static void closed( ) {
f9fc992002-06-12Honza Petrous  catch(fd->set_blocking( )); //hop@: zachyceno. BTW: nejspis je zbytecne?
650fea2001-04-10Per Hedbor  fd = 0; stream->remove_callback( callback );
f9fc992002-06-12Honza Petrous  stream->get_playlist()->remove_md_callback( md_callback );
650fea2001-04-10Per Hedbor  _ccb( this_object() );
6877ce2001-04-11Per Hedbor  werror("Closed from client side\n"); // destruct( this_object() );
650fea2001-04-10Per Hedbor  }
b779eb2001-04-10Per Hedbor  static void create( Stdio.File _fd, string buffer, string prot, int _meta, MPEGStream _stream,
f9fc992002-06-12Honza Petrous  Location _loc, function _closed, int _cudp )
650fea2001-04-10Per Hedbor  {
d3cea92001-05-16Per Hedbor  location = _loc;
b779eb2001-04-10Per Hedbor  protocol = prot;
650fea2001-04-10Per Hedbor  fd = _fd;
b779eb2001-04-10Per Hedbor  do_meta = _meta;
650fea2001-04-10Per Hedbor  stream = _stream; _ccb = _closed;
f9fc992002-06-12Honza Petrous  cudp = _cudp; claddr = (fd->query_address()/" ")[0];
650fea2001-04-10Per Hedbor  current_block = buffer;
8626872001-04-17Per Hedbor  fd->set_nonblocking( lambda(){}, send_more, closed );
b779eb2001-04-10Per Hedbor  if( stream ) stream->add_callback( callback );
f9fc992002-06-12Honza Petrous  if( stream->get_playlist() ) { md_callback(stream->get_playlist()->metadata()); stream->get_playlist()->add_md_callback( md_callback ); }
650fea2001-04-10Per Hedbor  } } class VarStreams { inherit Variable.List; constant type="StreamList";
c0c9b92001-04-11Per Hedbor  // loc
650fea2001-04-10Per Hedbor  // playlist // jingle // maxconn
c0c9b92001-04-11Per Hedbor  // URL // name
650fea2001-04-10Per Hedbor  string render_row( string prefix, mixed val, int width ) { string res = "<input type=hidden name='"+prefix+"' value='"+prefix+"' />";
c0c9b92001-04-11Per Hedbor  mapping m = decode_stream( val ); res += "<table>"; res += "<tr><td>Location "+query_location()+"</td><td>"; res += ("<input name='"+prefix+"loc' value='"+ Roxen.html_encode_string(m->location||"")+"' size=20/>"); res += "</td><td>Source</td><td>";
650fea2001-04-10Per Hedbor  res += "<select name='"+prefix+"playlist'>"; mapping pl; foreach( sort(indices(pl=playlists(0))), string p )
c0c9b92001-04-11Per Hedbor  if( pl[ p ]->sname() == m->playlist ) res +="<option value='"+pl[p]->sname()+"' selected='t'>"+p+"</option>";
650fea2001-04-10Per Hedbor  else
c0c9b92001-04-11Per Hedbor  res +="<option value='"+pl[p]->sname()+"'>"+p+"</option>"; res += "</select></td></tr><tr>"; res += ("<td>Max. conn</td><td><input name='"+prefix+"conn' value='"+ (m->maxconn||10)+"' size=5/></td> "); res += ("<td>Name</td><td> <input name='"+prefix+"name' value='"+ Roxen.html_encode_string(m->name || "")+"' size=20/></td></tr>"); res += ("<tr><td>Jingle</td><td colspan=3>" "<input name='"+prefix+"jingle' value='"+ Roxen.html_encode_string(m->jingle || "")+"' size=50/></td></tr>"); res += ("<td>URL</td><td colspan=3> <input name='"+prefix+"url' value='"+ Roxen.html_encode_string(m->url || "")+"' size=50/></td>"); return res+"</tr></table>";
650fea2001-04-10Per Hedbor  } 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"]
c0c9b92001-04-11Per Hedbor  +"\0"+va[v+"conn"]+"\0"+va[v+"url"]+"\0"+va[v+"name"];
650fea2001-04-10Per Hedbor  }
c0c9b92001-04-11Per Hedbor  mapping decode_stream( string s ) { mapping m = ([]); array a = s/"\0"; m->location = a[0]; if( sizeof( a ) > 1 ) m->playlist = a[1]; if( sizeof( a ) > 2 ) m->jingle = a[2]; if( sizeof( a ) > 3 ) m->maxconn = (int)a[3]; if( sizeof( a ) > 4 ) m->url = a[4]; if( sizeof( a ) > 5 ) m->name = a[5];
d3cea92001-05-16Per Hedbor  if( !m->jingle || !strlen( m->jingle ) ) m_delete( m, "jingle" ); if( !m->url || !strlen( m->url ) ) m_delete( m, "url" ); if( !m->name || !strlen( m->name ) ) m_delete( m, "name" );
c0c9b92001-04-11Per Hedbor  return m; }
650fea2001-04-10Per Hedbor  array(mapping) get_streams() {
c0c9b92001-04-11Per Hedbor  return map( query(), decode_stream );
650fea2001-04-10Per Hedbor  } } void create() { defvar( "streams", VarStreams( ({}), 0, _(0,"Streams"), _(0,"All the streams") ) );
f9fc992002-06-12Honza Petrous 
650fea2001-04-10Per Hedbor  defvar("location", "/strm/", _(0,"Mount point"), TYPE_LOCATION|VAR_INITIAL, _(0,"Where the module will be mounted in the site's virtual " "file system."));
f9fc992002-06-12Honza Petrous  defvar("udpmeta", 0, _(0,"UDP port"), TYPE_INT|VAR_MORE, _(0,"Port number for out of band metadata exchange. Note: Works only " "for Audiocast clients (like FreeAmp). " "<br /><b>Zero disables support</b>.")); defvar("mdtitle", "%artist%: %album% [%year%]: %title%", _(0,"Stream title"), TYPE_STRING|VAR_MORE, _(0,"The template for title of stream<br />" "Usable macros are:" "<ul>" "<li>%artist%<br />" "Performer(s)/Soloist(s) <i>(TPE1 in v2.3+)</i><br /></li>" "<li>%album%<br />" "Album/Movie/Show title <i>(TALB in v2.3+)</i><br /></li>" "<li>%title%<br />" "Title/songname/content description " "<i>(TIT2 in v2.3+)</i><br /></li>" "<li>%track%<br />" "Album/Movie/Show title <i>(TRCK in v2.3+)</i><br /></li>" "<li>%year%<br />" "A year of the recording<i>(TYER in v2.3+)</i><br /></li>" "</ul>" ".")); defvar("pllen", 0, _(0,"Playlist lenght"), TYPE_INT|VAR_MORE, _(0,"Max. lenght of returned playlist. Useful for long playlists." "<br /><b>Zero means full lenght</b>."));
650fea2001-04-10Per Hedbor } 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 = ([ ]);
b779eb2001-04-10Per Hedbor void st2()
650fea2001-04-10Per Hedbor { mapping pl = playlists( 1 ); foreach( getvar( "streams" )->get_streams(), mapping strm ) { MPEGStream mps;
f9fc992002-06-12Honza Petrous werror("DEB: strm: %O\n", strm);
b779eb2001-04-10Per Hedbor  if( strm->playlist && pl[ strm->playlist ] )
650fea2001-04-10Per Hedbor  if( !(mps = streams[ pl[strm->playlist] ]) ) { mps = streams[pl[strm->playlist]] = MPEGStream( pl[strm->playlist] ); mps->start(); }
b779eb2001-04-10Per Hedbor  if( !mps ) continue;
650fea2001-04-10Per Hedbor  if( !locations[ strm->location ] ) locations[ strm->location ] = Location( strm->location, (strm->jingle? Stdio.read_file( strm->jingle ) : 0 ), mps,
c0c9b92001-04-11Per Hedbor  (int)strm->maxconn, strm->url, strm->name );
650fea2001-04-10Per Hedbor  } }
f9fc992002-06-12Honza Petrous void start(int occasion, Configuration conf)
b779eb2001-04-10Per Hedbor {
f9fc992002-06-12Honza Petrous  call_out( st2, 1 ); // udpstreaming if(udpstream) udpstream = 0; if(query("udpmeta")) { udpstream = Stdio.UDP(); mixed err = catch( udpstream->bind(query("udpmeta"), Standards.URI(conf->query("MyWorldLocation"))->host) ); if(err) { udpstream = 0; werror("UDP title streaming is disabled. Reason: "+err[0]+"\n"); } else werror("UDP title streaming port [%s] is opened.\n", (udpstream->query_address()/" ")*":"); }
b779eb2001-04-10Per Hedbor }
650fea2001-04-10Per Hedbor mapping find_file( string f, RequestID id ) {
f9fc992002-06-12Honza Petrous  if(id->query && stringp(id->query) && sizeof(id->query)) { array qs = id->query / "?"; // note: only first will be used switch((qs[0]/"=")[0]) { case "playlist": // playlist request string pls; int len; if(sizeof(qs[0]/"=") > 1) len = (int)((qs[0]/"=")[1]); else len = query("pllen"); if(len) pls = locations[f]->stream->get_playlist()->list[..len-1] * "\r\n"; else pls = locations[f]->stream->get_playlist()->list * "\r\n"; q_playlist++; #if 1 return Roxen.http_string_answer(pls, "text/plain"); #else return Roxen.http_string_answer(pls, "audio/x-mpegurl"); #endif break; case "cmd-next": // forward request locations[f]->stream->cmd = "forward"; break; case "cmd-prev": // back request locations[f]->stream->cmd = "back"; break; } return 0; }
650fea2001-04-10Per Hedbor  if( locations[f] ) return locations[f]->handle( id ); return 0; }
b779eb2001-04-10Per Hedbor  string status() {
f9fc992002-06-12Honza Petrous  string res = udpstream ? "<p>UDP title streaming port: " + query("udpmeta") + "</p>\n" : ""; res += "<p>Playlist queries: " + q_playlist + "</p>\n"; res += "<h3>Streams</h3><table>";
b779eb2001-04-10Per Hedbor  foreach( indices( locations ), string loc ) { res += "<tr><td valign=top>"+loc+"</td><td valign=top>"+ locations[loc]->status()+"</td></tr>"; } return res+"</table>"; }
f9fc992002-06-12Honza Petrous  // -hop@ class TagEmitKnownMP3streams { inherit RXML.Tag; constant name = "emit", plugin_name = "mp3-stream"; array get_dataset(mapping m, RequestID id) { if(m->name) if(locations[m->name]) return ({ loc_description(m->name, (int)m->listmax, m->delimiter) }); else return 0; return map(indices(locations), loc_description); } private mapping loc_description(string loc, int maxlist, string delim) { return (["name": loc, "accessed": locations[loc]->accessed, "denied": locations[loc]->denied, "connections": locations[loc]->connections, "avg-listen-time": locations[loc]->avg_listen_time(), "longest-listen-time": locations[loc]->longest_listen_time(), "real-dir": locations[loc]->stream->get_playlist()->real_dir(), "current-song": locations[loc]->stream->get_playlist()->current_filename() || "", "next-song": locations[loc]->stream->get_playlist()->peek_next() || "", #if 0 "conn": map(locations[loc]->fd->query_address()/" ")[0] #endif "playlist": maxlist ? locations[loc]->stream->get_playlist()->list[..maxlist-1] * (delim || "\0") : locations[loc]->stream->get_playlist()->list * (delim || "\0") ]); } }