stream.c
branchtrunk
changeset 0 474a1293c3c0
child 9 dc75c2890a31
     1.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     1.2 +++ b/stream.c	Sat Dec 29 14:47:40 2007 +0100
     1.3 @@ -0,0 +1,589 @@
     1.4 +/*
     1.5 + * MP3/MPlayer plugin to VDR (C++)
     1.6 + *
     1.7 + * (C) 2001-2006 Stefan Huelswitt <s.huelswitt@gmx.de>
     1.8 + *
     1.9 + * This code is free software; you can redistribute it and/or
    1.10 + * modify it under the terms of the GNU General Public License
    1.11 + * as published by the Free Software Foundation; either version 2
    1.12 + * of the License, or (at your option) any later version.
    1.13 + *
    1.14 + * This code is distributed in the hope that it will be useful,
    1.15 + * but WITHOUT ANY WARRANTY; without even the implied warranty of
    1.16 + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    1.17 + * GNU General Public License for more details.
    1.18 + *
    1.19 + * You should have received a copy of the GNU General Public License
    1.20 + * along with this program; if not, write to the Free Software
    1.21 + * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
    1.22 + * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
    1.23 + */
    1.24 +
    1.25 +#include <stdlib.h>
    1.26 +#include <stdio.h>
    1.27 +#include <unistd.h>
    1.28 +#include <errno.h>
    1.29 +
    1.30 +#include "common.h"
    1.31 +#include "setup-mp3.h"
    1.32 +#include "stream.h"
    1.33 +#include "network.h"
    1.34 +#include "menu-async.h"
    1.35 +#include "i18n.h"
    1.36 +#include "version.h"
    1.37 +
    1.38 +#define USE_MMAP
    1.39 +#define MAX_MMAP_SIZE   (32*1024*1024)
    1.40 +#define MP3FILE_BUFSIZE (32*1024)
    1.41 +
    1.42 +#ifdef USE_MMAP
    1.43 +#include <sys/mman.h>
    1.44 +#endif
    1.45 +
    1.46 +#define DEFAULT_PORT 80 // default port for streaming (HTTP)
    1.47 +
    1.48 +//#define DUMP_HEAD "/var/tmp/headers"
    1.49 +
    1.50 +// --- cIO ---------------------------------------------------------------------
    1.51 +
    1.52 +#if 0
    1.53 +cIO::cIO(const char *Filename, bool Log)
    1.54 +:cFileInfo(Filename)
    1.55 +{
    1.56 +  log=Log;
    1.57 +  readpos=0;
    1.58 +}
    1.59 +
    1.60 +cIO::~cIO()
    1.61 +{
    1.62 +}
    1.63 +
    1.64 +// --- cStreamIO ---------------------------------------------------------------
    1.65 +
    1.66 +cStreamIO::cStreamIO(void)
    1.67 +{
    1.68 +  data=0;
    1.69 +}
    1.70 +
    1.71 +cStreamIO::~cStreamIO()
    1.72 +{
    1.73 +  StreamClear(true);
    1.74 +}
    1.75 +
    1.76 +void cStreamIO::StreamClear(bool all)
    1.77 +{
    1.78 +  if(all) { free(data); data=0; }
    1.79 +  fill=0;
    1.80 +}
    1.81 +
    1.82 +unsigned char *cStreamIO::StreamInit(int Size)
    1.83 +{
    1.84 +  StreamClear(true);
    1.85 +  size=Size; data=(unsigned char *)malloc(size);
    1.86 +  return data;
    1.87 +}
    1.88 +
    1.89 +int cStreamIO::Stream(const unsigned char *rest)
    1.90 +{
    1.91 +  if(rest && fill) { // copy remaining data to start of buffer
    1.92 +    fill-=(rest-data);
    1.93 +    memmove(data,rest,fill);
    1.94 +    }
    1.95 +  else fill=0;
    1.96 +
    1.97 +  unsigned long r=Read(data+fill,size-fill);
    1.98 +  if(r>=0) { 
    1.99 +    fill+=r;
   1.100 +    return fill;
   1.101 +    }
   1.102 +  return -1;    
   1.103 +}
   1.104 +
   1.105 +// --- cFileIO -----------------------------------------------------------------
   1.106 +
   1.107 +cFileIO::cFileIO(const char *Filename, bool Log)
   1.108 +:cIO(Filename,Log)
   1.109 +{
   1.110 +  fd=-1;
   1.111 +}
   1.112 +
   1.113 +cFileIO::~cFileIO()
   1.114 +{
   1.115 +  Close();
   1.116 +}
   1.117 +
   1.118 +bool cFileIO::Open(void)
   1.119 +{
   1.120 +  if(fd>=0) return Seek(0,SEEK_SET);
   1.121 +  if(FileInfo(log)) {
   1.122 +    if((fd=open(Filename,O_RDONLY))>=0) {
   1.123 +      StreamClear(false);
   1.124 +      readpos=0;
   1.125 +      return true;
   1.126 +      }
   1.127 +    else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); }
   1.128 +    }
   1.129 +  Close();
   1.130 +  return false;
   1.131 +}
   1.132 +
   1.133 +void cFileIO::Close(void)
   1.134 +{
   1.135 +  if(fd>=0) { close(fd); fd=-1; }
   1.136 +}
   1.137 +
   1.138 +int cFileIO::Read(unsigned char *Data, int Size)
   1.139 +{
   1.140 +  unsigned long r=read(fd,Data,Size);
   1.141 +  if(r>=0) readpos+=r;
   1.142 +  else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); }
   1.143 +  return r;
   1.144 +}
   1.145 +
   1.146 +bool cFileIO::Seek(unsigned long long pos, int whence)
   1.147 +{
   1.148 +  if(pos>=0 && pos<=Filesize) {
   1.149 +    StreamClear(false);
   1.150 +    if((readpos=lseek64(fd,pos,whence))>=0) {
   1.151 +      if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
   1.152 +      return true;
   1.153 +      }
   1.154 +    else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); }
   1.155 +    }
   1.156 +  else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
   1.157 +  return false;
   1.158 +}
   1.159 +#endif
   1.160 +
   1.161 +// --- cStream -----------------------------------------------------------------
   1.162 +
   1.163 +cStream::cStream(const char *Filename)
   1.164 +:cFileInfo(Filename)
   1.165 +{
   1.166 +  fd=-1; ismmap=false; buffer=0;
   1.167 +}
   1.168 +
   1.169 +cStream::~cStream()
   1.170 +{
   1.171 +  Close();
   1.172 +}
   1.173 +
   1.174 +bool cStream::Open(bool log)
   1.175 +{
   1.176 +  if(fd>=0) return Seek();
   1.177 +
   1.178 +  if(FileInfo(log)) {
   1.179 +    if((fd=open(Filename,O_RDONLY))>=0) {
   1.180 +      buffpos=readpos=0; fill=0;
   1.181 +
   1.182 +#ifdef USE_MMAP
   1.183 +      if(Filesize<=MAX_MMAP_SIZE) {
   1.184 +        buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0);
   1.185 +        if(buffer!=MAP_FAILED) {
   1.186 +          ismmap=true;
   1.187 +          return true;
   1.188 +          }
   1.189 +        else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno));
   1.190 +        }
   1.191 +#endif
   1.192 +
   1.193 +        buffer = new unsigned char[MP3FILE_BUFSIZE];
   1.194 +        if(buffer) return true;
   1.195 +        else { esyslog("ERROR: not enough memory for buffer: %s",Filename); }
   1.196 +      }
   1.197 +    else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
   1.198 +    }
   1.199 +
   1.200 +  Close();
   1.201 +  return false;
   1.202 +}
   1.203 +
   1.204 +void cStream::Close(void)
   1.205 +{
   1.206 +#ifdef USE_MMAP
   1.207 +  if(ismmap) { 
   1.208 +    munmap(buffer,Filesize); buffer=0; ismmap=false;
   1.209 +    }
   1.210 +  else {
   1.211 +#endif
   1.212 +    delete buffer; buffer=0;
   1.213 +#ifdef USE_MMAP
   1.214 +    }
   1.215 +#endif
   1.216 +  if(fd>=0) { close(fd); fd=-1; }
   1.217 +}
   1.218 +
   1.219 +bool cStream::Seek(unsigned long long pos)
   1.220 +{
   1.221 +  if(fd>=0 && pos>=0 && pos<=Filesize) {
   1.222 +    buffpos=0; fill=0;
   1.223 +    if(ismmap) {
   1.224 +      readpos=pos;
   1.225 +      return true;
   1.226 +      }
   1.227 +    else {
   1.228 +      if((readpos=lseek64(fd,pos,SEEK_SET))>=0) {
   1.229 +        if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
   1.230 +        return true;
   1.231 +        }
   1.232 +      else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); }
   1.233 +      }
   1.234 +    }
   1.235 +  else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
   1.236 +  return false;
   1.237 +}
   1.238 +
   1.239 +bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
   1.240 +{
   1.241 +  if(fd>=0) {
   1.242 +    if(readpos<Filesize) {
   1.243 +      if(ismmap) {
   1.244 +        if(rest && fill) readpos=(rest-buffer);   // take care of remaining data
   1.245 +        fill=Filesize-readpos;
   1.246 +        data=buffer+readpos; len=fill;
   1.247 +        buffpos=readpos; readpos+=fill;
   1.248 +        return true;
   1.249 +        }
   1.250 +      else {
   1.251 +        if(rest && fill) {       // copy remaining data to start of buffer
   1.252 +          fill-=(rest-buffer);   // remaing bytes
   1.253 +          memmove(buffer,rest,fill);
   1.254 +          }
   1.255 +        else fill=0;
   1.256 +
   1.257 +        int r;
   1.258 +        do { 
   1.259 +          r=read(fd,buffer+fill,MP3FILE_BUFSIZE-fill);
   1.260 +          } while(r==-1 && errno==EINTR);
   1.261 +
   1.262 +        if(r>=0) {
   1.263 +          buffpos=readpos-fill; readpos+=r; fill+=r;
   1.264 +          data=buffer; len=fill;
   1.265 +          return true;
   1.266 +          }
   1.267 +        else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); }
   1.268 +        }
   1.269 +      }
   1.270 +    else {
   1.271 +      len=0;
   1.272 +      return true;
   1.273 +      }
   1.274 +    }
   1.275 +  return false;
   1.276 +}
   1.277 +
   1.278 +// -----------------------------------------------------------------------------
   1.279 +
   1.280 +#ifdef DUMP_HEAD
   1.281 +void Dump(const char *name, char *buffer)
   1.282 +{
   1.283 +  FILE *f=fopen(name,"a");
   1.284 +  if(f) {
   1.285 +    fprintf(f,"<<<< %s\n",buffer);
   1.286 +/*
   1.287 +    int n=strlen(buffer);
   1.288 +    for(int i=0 ; i<n ; i+=8) {
   1.289 +      fprintf(f,"%04x: ",i);
   1.290 +      for(int l=0 ; l<8 && i+l<n ; l++) fprintf(f,"%02x ",buffer[i+l]);
   1.291 +      fprintf(f,"\n");
   1.292 +      }
   1.293 +*/
   1.294 +    fclose(f);
   1.295 +    }
   1.296 +}
   1.297 +#endif
   1.298 +
   1.299 +// --- cNetStream -----------------------------------------------------------------
   1.300 +
   1.301 +cNetStream::cNetStream(const char *Filename)
   1.302 +:cStream(Filename)
   1.303 +{
   1.304 +  net=0; host=path=auth=0; cc=0;
   1.305 +  icyName=icyUrl=icyTitle=0; icyChanged=false;
   1.306 +  metaInt=0;
   1.307 +  InfoDone();
   1.308 +}
   1.309 +
   1.310 +cNetStream::~cNetStream()
   1.311 +{
   1.312 +  free(host); free(path); free(auth);
   1.313 +  free(icyName); free(icyUrl); free(icyTitle);
   1.314 +}
   1.315 +
   1.316 +bool cNetStream::ParseURL(const char *line, bool log)
   1.317 +{
   1.318 +  char pr[32], h[512], p[512], a[512];
   1.319 +  int r=sscanf(line," %31[^:]://%511[^/]%511[^\r\n]",pr,h,p);
   1.320 +  if(r==2) {
   1.321 +    d(printf("netstream: adding default path '/'\n"))
   1.322 +    strcpy(p,"/");
   1.323 +    r++;
   1.324 +    }
   1.325 +  if(r==3) {
   1.326 +    a[0]=0;
   1.327 +    char *s=index(h,'@');
   1.328 +    if(s) {
   1.329 +      *s=0;
   1.330 +      strcpy(a,h);
   1.331 +      strcpy(h,s+1);
   1.332 +      }
   1.333 +    d(printf("netstream: parsed proto='%s' host='%s' path='%s' auth='%s'\n",pr,h,p,a))
   1.334 +    if(!strcasecmp(pr,"http")) {
   1.335 +      int pp=DEFAULT_PORT;
   1.336 +      s=rindex(h,':');
   1.337 +      if(s) { *s++=0; pp=atoi(s); }
   1.338 +
   1.339 +      free(host); host=strdup(h);
   1.340 +      free(path); path=strdup(p);
   1.341 +      free(auth); auth=a[0] ? strdup(a) : 0;
   1.342 +      port=pp;
   1.343 +      return true;
   1.344 +      }
   1.345 +    else if(log) esyslog("Unsupported protocol %s in: %s",pr,line);
   1.346 +    }
   1.347 +  else if(log) esyslog("Bad URL line: %s",line);
   1.348 +  return false;    
   1.349 +}
   1.350 +
   1.351 +bool cNetStream::ParseURLFile(const char *name, bool log)
   1.352 +{
   1.353 +  bool res=false;
   1.354 +  FILE *f=fopen(name,"r");
   1.355 +  if(f) {
   1.356 +    char line[2048];
   1.357 +    if(fgets(line,sizeof(line),f)) {
   1.358 +      res=ParseURL(line,log);
   1.359 +      }
   1.360 +    else if(log) esyslog("Nothing to read from URL file %s. File empty?",name);
   1.361 +    fclose(f);
   1.362 +    }
   1.363 +  else if(log) esyslog("fopen() failed on URL file %s: %s",name,strerror(errno));
   1.364 +  return res;
   1.365 +}
   1.366 +
   1.367 +bool cNetStream::SendRequest(void)
   1.368 +{
   1.369 +  bool res=false;
   1.370 +  char buff[2048];
   1.371 +
   1.372 +  char *h, *p;
   1.373 +  asprintf(&h,port!=DEFAULT_PORT ? "%s:%d":"%s",host,port);
   1.374 +  if(MP3Setup.UseProxy) asprintf(&p,"http://%s%s",h,path);
   1.375 +  else asprintf(&p,"%s",path);
   1.376 +
   1.377 +  char a[1024];
   1.378 +  a[0]=0;
   1.379 +  if(auth) {
   1.380 +    cBase64Encoder b64((uchar *)auth,strlen(auth),76);
   1.381 +    int q=0;
   1.382 +    const char *l;
   1.383 +    while((l=b64.NextLine())) {
   1.384 +      q+=snprintf(&a[q],sizeof(a)-q,"%s%s\r\n",q==0?"Authorization: Basic ":" ",l);
   1.385 +      }
   1.386 +    }
   1.387 +
   1.388 +  snprintf(buff,sizeof(buff),
   1.389 +           "GET %s HTTP/1.0\r\n"
   1.390 +           "User-Agent: %s/%s\r\n"
   1.391 +           "Host: %s\r\n"
   1.392 +           "Accept: audio/mpeg\r\n"   //XXX audio/x-mpegurl, */*
   1.393 +           "Icy-MetaData: 1\r\n"
   1.394 +           "%s\r\n",
   1.395 +           p,PLUGIN_NAME,PLUGIN_VERSION,h,a);
   1.396 +  free(p); free(h);  
   1.397 +
   1.398 +  if(++cc==1) asyncStatus.Set(tr("Connecting to stream server ..."));
   1.399 +
   1.400 +  if(net->Connect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) {
   1.401 +    d(printf("netstream: -> %s",buff))
   1.402 +    if(net->Puts(buff)>0) res=GetHTTPResponse();
   1.403 +    }
   1.404 +
   1.405 +  if(cc--==1) asyncStatus.Set(0);
   1.406 +  return res;
   1.407 +}
   1.408 +
   1.409 +bool cNetStream::ParseHeader(const char *buff, const char *name, char **value)
   1.410 +{
   1.411 +  char *s=index(buff,':');
   1.412 +  if(s && !strncasecmp(buff,name,s-buff)) {
   1.413 +    s=skipspace(s+1);
   1.414 +    d(printf("netstream: found header '%s' contents '%s'\n",name,s))
   1.415 +    free(*value); *value=strdup(s);
   1.416 +    return true;
   1.417 +    }
   1.418 +  return false;
   1.419 +}
   1.420 +
   1.421 +bool cNetStream::GetHTTPResponse(void)
   1.422 +{
   1.423 +  bool res=false;
   1.424 +  char buff[1024], text[128], *newurl=0;
   1.425 +  int code=-1, hcount=0;
   1.426 +  while(net->Gets(buff,sizeof(buff))>0) {
   1.427 +    stripspace(buff);
   1.428 +#ifdef DUMP_HEAD
   1.429 +    Dump(DUMP_HEAD,buff);
   1.430 +#endif
   1.431 +    d(printf("netstream: <- %s\n",buff))
   1.432 +    hcount++;
   1.433 +    if(hcount==1) {   // parse status line
   1.434 +      if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) {
   1.435 +        esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port);
   1.436 +        goto out;
   1.437 +        }
   1.438 +      }
   1.439 +    else {            // parse header lines
   1.440 +      if(buff[0]==0) { // headers finish if we receive a empty line
   1.441 +        switch(code) {
   1.442 +          case 200: // OK
   1.443 +             res=true;
   1.444 +             goto out;
   1.445 +          case 300: // MULTIPLE_CHOICES
   1.446 +          case 301: // MOVED_PERMANENTLY
   1.447 +          case 302: // MOVED_TEMPORARILY
   1.448 +             if(newurl) {
   1.449 +               if(ParseURL(newurl,true)) res=SendRequest();
   1.450 +               }
   1.451 +             else esyslog("No location header for redirection from %s:%d",host,port);
   1.452 +             goto out;
   1.453 +          default:
   1.454 +             esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port);
   1.455 +             goto out;
   1.456 +          }
   1.457 +        }
   1.458 +
   1.459 +      ParseHeader(buff,"Location",&newurl);
   1.460 +      ParseHeader(buff,"icy-name",&icyName);
   1.461 +      ParseHeader(buff,"icy-url",&icyUrl);
   1.462 +      char *meta=0;
   1.463 +      if(ParseHeader(buff,"icy-metaint",&meta)) {
   1.464 +        metaInt=metaCnt=atol(meta);
   1.465 +        d(printf("netstream: meta interval set to %d\n",metaInt));
   1.466 +        }
   1.467 +      free(meta);
   1.468 +      }
   1.469 +    }
   1.470 +out:
   1.471 +  free(newurl);
   1.472 +  return res;
   1.473 +}
   1.474 +
   1.475 +bool cNetStream::Open(bool log)
   1.476 +{
   1.477 +  if(net && net->Connected()) return true;
   1.478 +
   1.479 +  if(!net) net=new cNet(0,0,0);
   1.480 +  net->Disconnect();
   1.481 +  
   1.482 +  if(ParseURLFile(Filename,log)) {
   1.483 +    buffpos=readpos=0; fill=0;
   1.484 +    buffer = new unsigned char[MP3FILE_BUFSIZE];
   1.485 +    if(buffer) {
   1.486 +      if(SendRequest()) {
   1.487 +        return true;
   1.488 +        }
   1.489 +      }
   1.490 +    else esyslog("Not enough memory for buffer");
   1.491 +    }
   1.492 +
   1.493 +  Close();
   1.494 +  return false;
   1.495 +}
   1.496 +
   1.497 +void cNetStream::Close(void)
   1.498 +{
   1.499 +  delete buffer; buffer=0;
   1.500 +  delete net; net=0;
   1.501 +}
   1.502 +
   1.503 +bool cNetStream::Seek(unsigned long long pos)
   1.504 +{
   1.505 +  return false;
   1.506 +}
   1.507 +
   1.508 +bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
   1.509 +{
   1.510 +  if(net && net->Connected()) {
   1.511 +    if(rest && fill) {       // copy remaining data to start of buffer
   1.512 +      fill-=(rest-buffer);   // remaing bytes
   1.513 +      memmove(buffer,rest,fill);
   1.514 +      }
   1.515 +    else fill=0;
   1.516 +
   1.517 +    int r=MP3FILE_BUFSIZE-fill;
   1.518 +    if(metaInt && r>metaCnt) r=metaCnt;
   1.519 +    r=net->Read(buffer+fill,r);
   1.520 +    if(r>=0) {
   1.521 +      fill+=r; data=buffer; len=fill;
   1.522 +      metaCnt-=r;
   1.523 +      if(metaInt && metaCnt<=0) {
   1.524 +        ParseMetaData();
   1.525 +        metaCnt=metaInt;
   1.526 +        }
   1.527 +      return true;
   1.528 +      }
   1.529 +    }
   1.530 +  return false;
   1.531 +}
   1.532 +
   1.533 +char *cNetStream::ParseMetaString(const char *buff, const char *name, char **value)
   1.534 +{
   1.535 +  char *s=index(buff,'=');
   1.536 +  if(s && !strncasecmp(buff,name,s-buff)) {
   1.537 +    char *end=index(s+2,'\'');
   1.538 +    if(s[1]=='\'' && end) {
   1.539 +      *end=0;
   1.540 +      s=stripspace(skipspace(s+2));
   1.541 +      if(strlen(s)>0) {
   1.542 +        d(printf("netstream: found metadata '%s' contents '%s'\n",name,s))
   1.543 +        free(*value); *value=strdup(s);
   1.544 +        }
   1.545 +      //else d(printf("netstream: found empty metadata '%s'\n",name))
   1.546 +      return end+1;
   1.547 +      }
   1.548 +    else d(printf("netstream: bad metadata format\n"))
   1.549 +    }
   1.550 +  return 0;
   1.551 +}
   1.552 +
   1.553 +bool cNetStream::ParseMetaData(void)
   1.554 +{
   1.555 +  unsigned char byte;
   1.556 +  int r=net->Read(&byte,1);
   1.557 +  if(r<=0) return false;
   1.558 +  int metalen=byte*16;
   1.559 +  if(metalen>0) {
   1.560 +    char data[metalen+1];
   1.561 +    data[metalen]=0;
   1.562 +    int cnt=0;
   1.563 +    do {
   1.564 +      r=net->Read((unsigned char *)data+cnt,metalen-cnt);
   1.565 +      if(r<=0) return false;
   1.566 +      cnt+=r;
   1.567 +      } while(cnt<metalen);
   1.568 +
   1.569 +#ifdef DUMP_HEAD
   1.570 +    Dump(DUMP_HEAD,data);
   1.571 +#endif
   1.572 +
   1.573 +    char *p=data;
   1.574 +    while(*p && p-data<metalen) {
   1.575 +      char *n;
   1.576 +      if((n=ParseMetaString(p,"StreamTitle",&icyTitle)) ||
   1.577 +         (n=ParseMetaString(p,"StreamUrl",&icyUrl))) {
   1.578 +        p=n;
   1.579 +        icyChanged=true;
   1.580 +        }
   1.581 +      else  p++;
   1.582 +      }
   1.583 +    }
   1.584 +  return true;
   1.585 +}
   1.586 +
   1.587 +bool cNetStream::IcyChanged(void)
   1.588 +{
   1.589 +  bool c=icyChanged;
   1.590 +  icyChanged=false;
   1.591 +  return c;
   1.592 +}