2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2007 Stefan Huelswitt <s.huelswitt@gmx.de>
6 * This code is free software; you can redistribute it and/or
7 * modify it under the terms of the GNU General Public License
8 * as published by the Free Software Foundation; either version 2
9 * of the License, or (at your option) any later version.
11 * This code is distributed in the hope that it will be useful,
12 * but WITHOUT ANY WARRANTY; without even the implied warranty of
13 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14 * GNU General Public License for more details.
16 * You should have received a copy of the GNU General Public License
17 * along with this program; if not, write to the Free Software
18 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
19 * Or, point your browser to http://www.gnu.org/copyleft/gpl.html
28 #include "setup-mp3.h"
31 #include "menu-async.h"
36 #define MAX_MMAP_SIZE (32*1024*1024)
37 #define MP3FILE_BUFSIZE (32*1024)
43 #define DEFAULT_PORT 80 // default port for streaming (HTTP)
45 //#define DUMP_HEAD "/var/tmp/headers"
47 // --- cIO ---------------------------------------------------------------------
50 cIO::cIO(const char *Filename, bool Log)
61 // --- cStreamIO ---------------------------------------------------------------
63 cStreamIO::cStreamIO(void)
68 cStreamIO::~cStreamIO()
73 void cStreamIO::StreamClear(bool all)
75 if(all) { free(data); data=0; }
79 unsigned char *cStreamIO::StreamInit(int Size)
82 size=Size; data=(unsigned char *)malloc(size);
86 int cStreamIO::Stream(const unsigned char *rest)
88 if(rest && fill) { // copy remaining data to start of buffer
90 memmove(data,rest,fill);
94 unsigned long r=Read(data+fill,size-fill);
102 // --- cFileIO -----------------------------------------------------------------
104 cFileIO::cFileIO(const char *Filename, bool Log)
115 bool cFileIO::Open(void)
117 if(fd>=0) return Seek(0,SEEK_SET);
119 if((fd=open(Filename,O_RDONLY))>=0) {
124 else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); }
130 void cFileIO::Close(void)
132 if(fd>=0) { close(fd); fd=-1; }
135 int cFileIO::Read(unsigned char *Data, int Size)
137 unsigned long r=read(fd,Data,Size);
139 else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); }
143 bool cFileIO::Seek(unsigned long long pos, int whence)
145 if(pos>=0 && pos<=Filesize) {
147 if((readpos=lseek64(fd,pos,whence))>=0) {
148 if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
151 else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); }
153 else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
158 // --- cStream -----------------------------------------------------------------
160 cStream::cStream(const char *Filename)
163 fd=-1; ismmap=false; buffer=0;
171 bool cStream::Open(bool log)
173 if(fd>=0) return Seek();
176 if((fd=open(Filename,O_RDONLY))>=0) {
177 buffpos=readpos=0; fill=0;
180 if(Filesize<=MAX_MMAP_SIZE) {
181 buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0);
182 if(buffer!=MAP_FAILED) {
186 else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno));
190 buffer = new unsigned char[MP3FILE_BUFSIZE];
191 if(buffer) return true;
192 else { esyslog("ERROR: not enough memory for buffer: %s",Filename); }
194 else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
201 void cStream::Close(void)
205 munmap(buffer,Filesize); buffer=0; ismmap=false;
209 delete buffer; buffer=0;
213 if(fd>=0) { close(fd); fd=-1; }
216 bool cStream::Seek(unsigned long long pos)
218 if(fd>=0 && pos>=0 && pos<=Filesize) {
225 if((readpos=lseek64(fd,pos,SEEK_SET))>=0) {
226 if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
229 else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); }
232 else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
236 bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
239 if(readpos<Filesize) {
241 if(rest && fill) readpos=(rest-buffer); // take care of remaining data
242 fill=Filesize-readpos;
243 data=buffer+readpos; len=fill;
244 buffpos=readpos; readpos+=fill;
248 if(rest && fill) { // copy remaining data to start of buffer
249 fill-=(rest-buffer); // remaing bytes
250 memmove(buffer,rest,fill);
256 r=read(fd,buffer+fill,MP3FILE_BUFSIZE-fill);
257 } while(r==-1 && errno==EINTR);
260 buffpos=readpos-fill; readpos+=r; fill+=r;
261 data=buffer; len=fill;
264 else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); }
275 // -----------------------------------------------------------------------------
278 void Dump(const char *name, char *buffer)
280 FILE *f=fopen(name,"a");
282 fprintf(f,"<<<< %s\n",buffer);
284 int n=strlen(buffer);
285 for(int i=0 ; i<n ; i+=8) {
286 fprintf(f,"%04x: ",i);
287 for(int l=0 ; l<8 && i+l<n ; l++) fprintf(f,"%02x ",buffer[i+l]);
296 // --- cNetStream -----------------------------------------------------------------
298 cNetStream::cNetStream(const char *Filename)
301 net=0; host=path=auth=0; cc=0;
302 icyName=icyUrl=icyTitle=0; icyChanged=false;
307 cNetStream::~cNetStream()
309 free(host); free(path); free(auth);
310 free(icyName); free(icyUrl); free(icyTitle);
313 bool cNetStream::ParseURL(const char *line, bool log)
315 char pr[32], h[512], p[512], a[512];
316 int r=sscanf(line," %31[^:]://%511[^/]%511[^\r\n]",pr,h,p);
318 d(printf("netstream: adding default path '/'\n"))
324 char *s=index(h,'@');
330 d(printf("netstream: parsed proto='%s' host='%s' path='%s' auth='%s'\n",pr,h,p,a))
331 if(!strcasecmp(pr,"http")) {
334 if(s) { *s++=0; pp=atoi(s); }
336 free(host); host=strdup(h);
337 free(path); path=strdup(p);
338 free(auth); auth=a[0] ? strdup(a) : 0;
342 else if(log) esyslog("Unsupported protocol %s in: %s",pr,line);
344 else if(log) esyslog("Bad URL line: %s",line);
348 bool cNetStream::ParseURLFile(const char *name, bool log)
351 FILE *f=fopen(name,"r");
354 if(fgets(line,sizeof(line),f)) {
355 res=ParseURL(line,log);
357 else if(log) esyslog("Nothing to read from URL file %s. File empty?",name);
360 else if(log) esyslog("fopen() failed on URL file %s: %s",name,strerror(errno));
364 bool cNetStream::SendRequest(void)
370 asprintf(&h,port!=DEFAULT_PORT ? "%s:%d":"%s",host,port);
371 if(MP3Setup.UseProxy) asprintf(&p,"http://%s%s",h,path);
372 else asprintf(&p,"%s",path);
377 cBase64Encoder b64((uchar *)auth,strlen(auth),76);
380 while((l=b64.NextLine())) {
381 q+=snprintf(&a[q],sizeof(a)-q,"%s%s\r\n",q==0?"Authorization: Basic ":" ",l);
385 snprintf(buff,sizeof(buff),
386 "GET %s HTTP/1.0\r\n"
387 "User-Agent: %s/%s\r\n"
389 "Accept: audio/mpeg\r\n" //XXX audio/x-mpegurl, */*
390 "Icy-MetaData: 1\r\n"
392 p,PLUGIN_NAME,PluginVersion,h,a);
395 if(++cc==1) asyncStatus.Set(tr("Connecting to stream server ..."));
397 if(net->Connect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) {
398 d(printf("netstream: -> %s",buff))
399 if(net->Puts(buff)>0) res=GetHTTPResponse();
402 if(cc--==1) asyncStatus.Set(0);
406 bool cNetStream::ParseHeader(const char *buff, const char *name, char **value)
408 char *s=index(buff,':');
409 if(s && !strncasecmp(buff,name,s-buff)) {
411 d(printf("netstream: found header '%s' contents '%s'\n",name,s))
412 free(*value); *value=strdup(s);
418 bool cNetStream::GetHTTPResponse(void)
421 char buff[1024], text[128], *newurl=0;
422 int code=-1, hcount=0;
423 while(net->Gets(buff,sizeof(buff))>0) {
426 Dump(DUMP_HEAD,buff);
428 d(printf("netstream: <- %s\n",buff))
430 if(hcount==1) { // parse status line
431 if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) {
432 esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port);
436 else { // parse header lines
437 if(buff[0]==0) { // headers finish if we receive a empty line
442 case 300: // MULTIPLE_CHOICES
443 case 301: // MOVED_PERMANENTLY
444 case 302: // MOVED_TEMPORARILY
446 if(ParseURL(newurl,true)) res=SendRequest();
448 else esyslog("No location header for redirection from %s:%d",host,port);
451 esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port);
456 ParseHeader(buff,"Location",&newurl);
457 ParseHeader(buff,"icy-name",&icyName);
458 ParseHeader(buff,"icy-url",&icyUrl);
460 if(ParseHeader(buff,"icy-metaint",&meta)) {
461 metaInt=metaCnt=atol(meta);
462 d(printf("netstream: meta interval set to %d\n",metaInt));
472 bool cNetStream::Open(bool log)
474 if(net && net->Connected()) return true;
476 if(!net) net=new cNet(0,0,0);
479 if(ParseURLFile(Filename,log)) {
480 buffpos=readpos=0; fill=0;
481 buffer = new unsigned char[MP3FILE_BUFSIZE];
487 else esyslog("Not enough memory for buffer");
494 void cNetStream::Close(void)
496 delete buffer; buffer=0;
500 bool cNetStream::Seek(unsigned long long pos)
505 bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
507 if(net && net->Connected()) {
508 if(rest && fill) { // copy remaining data to start of buffer
509 fill-=(rest-buffer); // remaing bytes
510 memmove(buffer,rest,fill);
514 int r=MP3FILE_BUFSIZE-fill;
515 if(metaInt && r>metaCnt) r=metaCnt;
516 r=net->Read(buffer+fill,r);
518 fill+=r; data=buffer; len=fill;
520 if(metaInt && metaCnt<=0) {
530 char *cNetStream::ParseMetaString(const char *buff, const char *name, char **value)
532 char *s=index(buff,'=');
533 if(s && !strncasecmp(buff,name,s-buff)) {
534 char *end=index(s+2,'\'');
535 if(s[1]=='\'' && end) {
537 s=stripspace(skipspace(s+2));
539 d(printf("netstream: found metadata '%s' contents '%s'\n",name,s))
540 free(*value); *value=strdup(s);
542 //else d(printf("netstream: found empty metadata '%s'\n",name))
545 else d(printf("netstream: bad metadata format\n"))
550 bool cNetStream::ParseMetaData(void)
553 int r=net->Read(&byte,1);
554 if(r<=0) return false;
557 char data[metalen+1];
561 r=net->Read((unsigned char *)data+cnt,metalen-cnt);
562 if(r<=0) return false;
564 } while(cnt<metalen);
567 Dump(DUMP_HEAD,data);
571 while(*p && p-data<metalen) {
573 if((n=ParseMetaString(p,"StreamTitle",&icyTitle)) ||
574 (n=ParseMetaString(p,"StreamUrl",&icyUrl))) {
584 bool cNetStream::IcyChanged(void)