2 * MP3/MPlayer plugin to VDR (C++)
4 * (C) 2001-2009 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"
32 #include "menu-async.h"
37 #define MAX_MMAP_SIZE (32*1024*1024)
38 #define MP3FILE_BUFSIZE (32*1024)
44 #define DEFAULT_PORT 80 // default port for streaming (HTTP)
46 //#define DUMP_HEAD "/var/tmp/headers"
48 // --- cIO ---------------------------------------------------------------------
51 cIO::cIO(const char *Filename, bool Log)
62 // --- cStreamIO ---------------------------------------------------------------
64 cStreamIO::cStreamIO(void)
69 cStreamIO::~cStreamIO()
74 void cStreamIO::StreamClear(bool all)
76 if(all) { free(data); data=0; }
80 unsigned char *cStreamIO::StreamInit(int Size)
83 size=Size; data=(unsigned char *)malloc(size);
87 int cStreamIO::Stream(const unsigned char *rest)
89 if(rest && fill) { // copy remaining data to start of buffer
91 memmove(data,rest,fill);
95 unsigned long r=Read(data+fill,size-fill);
103 // --- cFileIO -----------------------------------------------------------------
105 cFileIO::cFileIO(const char *Filename, bool Log)
116 bool cFileIO::Open(void)
118 if(fd>=0) return Seek(0,SEEK_SET);
120 if((fd=open(Filename,O_RDONLY))>=0) {
125 else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); }
131 void cFileIO::Close(void)
133 if(fd>=0) { close(fd); fd=-1; }
136 int cFileIO::Read(unsigned char *Data, int Size)
138 unsigned long r=read(fd,Data,Size);
140 else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); }
144 bool cFileIO::Seek(unsigned long long pos, int whence)
146 if(pos>=0 && pos<=Filesize) {
148 if((readpos=lseek64(fd,pos,whence))>=0) {
149 if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
152 else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); }
154 else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
159 // --- cStream -----------------------------------------------------------------
161 cStream::cStream(const char *Filename)
164 fd=-1; ismmap=false; buffer=0;
172 bool cStream::Open(bool log)
174 if(fd>=0) return Seek();
177 if((fd=open(Filename,O_RDONLY))>=0) {
178 buffpos=readpos=0; fill=0;
181 if(Filesize<=MAX_MMAP_SIZE) {
182 buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0);
183 if(buffer!=MAP_FAILED) {
187 else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno));
191 buffer = new unsigned char[MP3FILE_BUFSIZE];
192 if(buffer) return true;
193 else { esyslog("ERROR: not enough memory for buffer: %s",Filename); }
195 else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); }
202 void cStream::Close(void)
206 munmap(buffer,Filesize); buffer=0; ismmap=false;
210 delete buffer; buffer=0;
214 if(fd>=0) { close(fd); fd=-1; }
217 bool cStream::Seek(unsigned long long pos)
219 if(fd>=0 && pos>=0 && pos<=Filesize) {
226 if((readpos=lseek64(fd,pos,SEEK_SET))>=0) {
227 if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); }
230 else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); }
233 else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename))
237 bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
240 if(readpos<Filesize) {
242 if(rest && fill) readpos=(rest-buffer); // take care of remaining data
243 fill=Filesize-readpos;
244 data=buffer+readpos; len=fill;
245 buffpos=readpos; readpos+=fill;
249 if(rest && fill) { // copy remaining data to start of buffer
250 fill-=(rest-buffer); // remaing bytes
251 memmove(buffer,rest,fill);
257 r=read(fd,buffer+fill,MP3FILE_BUFSIZE-fill);
258 } while(r==-1 && errno==EINTR);
261 buffpos=readpos-fill; readpos+=r; fill+=r;
262 data=buffer; len=fill;
265 else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); }
276 // -----------------------------------------------------------------------------
279 void Dump(const char *name, char *buffer)
281 FILE *f=fopen(name,"a");
283 fprintf(f,"<<<< %s\n",buffer);
285 int n=strlen(buffer);
286 for(int i=0 ; i<n ; i+=8) {
287 fprintf(f,"%04x: ",i);
288 for(int l=0 ; l<8 && i+l<n ; l++) fprintf(f,"%02x ",buffer[i+l]);
297 // --- cNetStream -----------------------------------------------------------------
299 cNetStream::cNetStream(const char *Filename)
302 net=0; host=path=auth=0; cc=0;
303 icyName=icyUrl=icyTitle=0; icyChanged=false;
308 cNetStream::~cNetStream()
310 free(host); free(path); free(auth);
311 free(icyName); free(icyUrl); free(icyTitle);
314 bool cNetStream::ParseURL(const char *line, bool log)
316 char pr[32], h[512], p[512], a[512];
317 int r=sscanf(line," %31[^:]://%511[^/]%511[^\r\n]",pr,h,p);
319 d(printf("netstream: adding default path '/'\n"))
325 char *s=index(h,'@');
331 d(printf("netstream: parsed proto='%s' host='%s' path='%s' auth='%s'\n",pr,h,p,a))
332 if(!strcasecmp(pr,"http")) {
335 if(s) { *s++=0; pp=atoi(s); }
337 free(host); host=strdup(h);
338 free(path); path=strdup(p);
339 free(auth); auth=a[0] ? strdup(a) : 0;
343 else if(log) esyslog("Unsupported protocol %s in: %s",pr,line);
345 else if(log) esyslog("Bad URL line: %s",line);
349 bool cNetStream::ParseURLFile(const char *name, bool log)
352 FILE *f=fopen(name,"r");
355 if(fgets(line,sizeof(line),f)) {
356 res=ParseURL(line,log);
358 else if(log) esyslog("Nothing to read from URL file %s. File empty?",name);
361 else if(log) esyslog("fopen() failed on URL file %s: %s",name,strerror(errno));
365 bool cNetStream::SendRequest(void)
371 char *h=aprintf(port!=DEFAULT_PORT ? "%s:%d":"%s",host,port);
372 if(MP3Setup.UseProxy) p=aprintf("http://%s%s",h,path);
373 else p=aprintf("%s",path);
378 cBase64Encoder b64((uchar *)auth,strlen(auth),76);
381 while((l=b64.NextLine())) {
382 q+=snprintf(&a[q],sizeof(a)-q,"%s%s\r\n",q==0?"Authorization: Basic ":" ",l);
386 snprintf(buff,sizeof(buff),
387 "GET %s HTTP/1.0\r\n"
388 "User-Agent: %s/%s\r\n"
390 "Accept: audio/mpeg\r\n" //XXX audio/x-mpegurl, */*
391 "Icy-MetaData: 1\r\n"
393 p,PLUGIN_NAME,PluginVersion,h,a);
396 if(++cc==1) asyncStatus.Set(tr("Connecting to stream server ..."));
398 if(net->Connect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) {
399 d(printf("netstream: -> %s",buff))
400 if(net->Puts(buff)>0) res=GetHTTPResponse();
403 if(cc--==1) asyncStatus.Set(0);
407 bool cNetStream::ParseHeader(const char *buff, const char *name, char **value)
409 const char *s=index(buff,':');
410 if(s && !strncasecmp(buff,name,s-buff)) {
412 d(printf("netstream: found header '%s' contents '%s'\n",name,s))
413 free(*value); *value=strdup(s);
419 bool cNetStream::GetHTTPResponse(void)
422 char buff[1024], text[128], *newurl=0;
423 int code=-1, hcount=0;
424 while(net->Gets(buff,sizeof(buff))>0) {
427 Dump(DUMP_HEAD,buff);
429 d(printf("netstream: <- %s\n",buff))
431 if(hcount==1) { // parse status line
432 if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) {
433 esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port);
437 else { // parse header lines
438 if(buff[0]==0) { // headers finish if we receive a empty line
443 case 300: // MULTIPLE_CHOICES
444 case 301: // MOVED_PERMANENTLY
445 case 302: // MOVED_TEMPORARILY
447 if(ParseURL(newurl,true)) res=SendRequest();
449 else esyslog("No location header for redirection from %s:%d",host,port);
452 esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port);
457 ParseHeader(buff,"Location",&newurl);
458 ParseHeader(buff,"icy-name",&icyName);
459 ParseHeader(buff,"icy-url",&icyUrl);
461 if(ParseHeader(buff,"icy-metaint",&meta)) {
462 metaInt=metaCnt=atol(meta);
463 d(printf("netstream: meta interval set to %d\n",metaInt));
473 bool cNetStream::Open(bool log)
475 if(net && net->Connected()) return true;
477 if(!net) net=new cNet(0,0,0);
480 if(ParseURLFile(Filename,log)) {
481 buffpos=readpos=0; fill=0;
482 buffer = new unsigned char[MP3FILE_BUFSIZE];
488 else esyslog("Not enough memory for buffer");
495 void cNetStream::Close(void)
497 delete buffer; buffer=0;
501 bool cNetStream::Seek(unsigned long long pos)
506 bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest)
508 if(net && net->Connected()) {
509 if(rest && fill) { // copy remaining data to start of buffer
510 fill-=(rest-buffer); // remaing bytes
511 memmove(buffer,rest,fill);
515 int r=MP3FILE_BUFSIZE-fill;
516 if(metaInt && r>metaCnt) r=metaCnt;
517 r=net->Read(buffer+fill,r);
519 fill+=r; data=buffer; len=fill;
521 if(metaInt && metaCnt<=0) {
531 char *cNetStream::ParseMetaString(char *buff, const char *name, char **value)
533 char *s=index(buff,'=');
534 if(s && !strncasecmp(buff,name,s-buff)) {
535 char *end=index(s+2,'\'');
536 if(s[1]=='\'' && end) {
538 s=stripspace(skipspace(s+2));
540 d(printf("netstream: found metadata '%s' contents '%s'\n",name,s))
541 free(*value); *value=strdup(s);
543 //else d(printf("netstream: found empty metadata '%s'\n",name))
546 else d(printf("netstream: bad metadata format\n"))
551 bool cNetStream::ParseMetaData(void)
554 int r=net->Read(&byte,1);
555 if(r<=0) return false;
558 char data[metalen+1];
562 r=net->Read((unsigned char *)data+cnt,metalen-cnt);
563 if(r<=0) return false;
565 } while(cnt<metalen);
568 Dump(DUMP_HEAD,data);
572 while(*p && p-data<metalen) {
574 if((n=ParseMetaString(p,"StreamTitle",&icyTitle)) ||
575 (n=ParseMetaString(p,"StreamUrl",&icyUrl))) {
585 bool cNetStream::IcyChanged(void)