Roxen.git / server / modules / icecast / icecast.pike

version» Context lines:

Roxen.git/server/modules/icecast/icecast.pike:1: + inherit "module"; + constant cvs_version="$Id: icecast.pike,v 1.1 2001/04/10 01:20:47 per Exp $"; + constant thread_safe=1;    -  + #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) +  + constant module_type = MODULE_LOCATION; + LocaleString module_name = _(0,"Icecast: Server"); + LocaleString module_doc = _(0,""); + 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; // 1000:th of a second. +  int stream_start = time()*1000+(int)(time(time())*1000); +  Stdio.File fd; +  +  void add_callback( function callback ) +  { +  callbacks += ({ callback }); +  } +  +  void remove_callback( function callback ) +  { +  callbacks -= ({ callback }); +  } +  +  void call_callbacks( mixed ... args ) +  { +  foreach( callbacks, function f ) +  if( catch( f(@args) ) ) +  callbacks -= ({f}); +  } +  +  int realtime() +  { +  return (time()*1000+(int)(time(time())*1000)) - stream_start; +  } +  +  void feeder_thread( ) +  { +  while( realtime() > stream_position ) +  { +  string frame = get_frame(); +  while( !frame ) +  { +  fd->close(); +  fd = playlist->next_file(); +  buffer = ""; +  bpos = 0; +  frame = get_frame(); +  } +  stream_position += strlen(frame)*8000 / bitrate; +  call_callbacks( frame ); +  }; +  call_out( feeder_thread, 0.02 ); +  } +  +  void start() +  { +  feeder_thread( ); // not actually a thread right now. +  } +  + #define BSIZE 8192 +  +  +  // Low level code. +  static string buffer; +  static int bpos; +  +  static string|int getbytes( int n, int|void s ) +  { +  if( !fd ) +  fd = playlist->next_file(); +  if( !buffer ) +  buffer = fd->read( BSIZE ); +  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; +  /* Find header */ +  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); // version +  if(!ID) /* not MPEG1 */ +  continue; +  layer = (4-getbits(2)); +  +  header<<=1; /* getbits(1); // error protection */ +  +  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; +  } +  +  return data + getbytes( blen,1 ); +  } +  } +  return 0; +  } + } +  + class Location( string location, +  string initial, +  MPEGStream stream, +  int max_connections ) +  + { +  int accessed, denied; +  int connections; +  +  array(Connection) conn = ({}); +  +  mapping handle( RequestID id ) +  { +  accessed++; +  if( connections == max_connections ) +  { +  denied++; +  return Roxen.http_string_answer( "Too many listeners\n" ); +  } +  +  connections++; +  +  string i = ("HTTP/1.0 200 OK\r\n" +  "Server: Roxen\r\n" +  "Content-type: audio/mpeg\r\n" +  "\r\n" ); +  +  if( initial ) +  i += initial; +  +  conn += ({ Connection( id->my_fd, i, +  stream, +  lambda( Connection c ){ +  conn-=({c}); +  connections--; +  } ) }); +  return Roxen.http_pipe_in_progress( ); +  } + } +  + mapping(string:Location) locations = ([]); +  + class Connection + { +  Stdio.File fd; +  MPEGStream stream; +  +  int sent, skipped; // frames. +  array buffer = ({}); +  string current_block; +  function _ccb; +  +  static void callback( string frame ) +  { +  buffer += ({ frame }); +  if( sizeof( buffer ) > 10 ) +  { +  skipped++; +  buffer = buffer[1..]; +  } +  if( sizeof( buffer ) == 1 ) +  send_more(); +  } +  +  static void send_more() +  { +  // FIXME: Check metadata here! +  if( !strlen(current_block) ) +  { +  if( !sizeof(buffer) ) +  return; +  current_block = buffer[0]; +  buffer = buffer[1..]; +  sent++; +  } +  int n = fd->write( current_block ); +  if( !n || n < 0 ) +  closed(); +  current_block = current_block[n..]; +  } +  +  static void closed( ) +  { +  fd = 0; +  stream->remove_callback( callback ); +  _ccb( this_object() ); +  destruct( this_object() ); +  } +  +  static void create( Stdio.File _fd, string buffer, +  MPEGStream _stream, +  function _closed ) +  { +  fd = _fd; +  stream = _stream; +  _ccb = _closed; +  current_block = buffer; +  fd->set_nonblocking( lambda(){}, send_more, closed ); +  stream->add_callback( callback ); +  } + } +  +  + class VarStreams + { +  inherit Variable.List; +  constant type="StreamList"; +  +  // location +  // playlist +  // jingle +  // maxconn +  +  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 = ([ ]); +  + void start() + { +  mapping pl = playlists( 1 ); +  foreach( getvar( "streams" )->get_streams(), mapping strm ) +  { +  MPEGStream mps; +  +  if( strm->playlist && pl[strm->playlist] ) +  if( !(mps = streams[ pl[strm->playlist] ]) ) +  { +  mps = streams[pl[strm->playlist]] = MPEGStream( pl[strm->playlist] ); +  mps->start(); +  } +  if( !locations[ strm->location ] ) +  locations[ strm->location ] = +  Location( strm->location, +  (strm->jingle? +  Stdio.read_file( strm->jingle ) : +  0 ), +  mps, +  (int)strm->maxconn ); +  } + } +  + mapping find_file( string f, RequestID id ) + { +  if( locations[f] ) +  return locations[f]->handle( id ); +  return 0; + }   Newline at end of file added.