nathan@0: /* nathan@0: * MP3/MPlayer plugin to VDR (C++) nathan@0: * nathan@34: * (C) 2001-2009 Stefan Huelswitt nathan@0: * nathan@0: * This code is free software; you can redistribute it and/or nathan@0: * modify it under the terms of the GNU General Public License nathan@0: * as published by the Free Software Foundation; either version 2 nathan@0: * of the License, or (at your option) any later version. nathan@0: * nathan@0: * This code is distributed in the hope that it will be useful, nathan@0: * but WITHOUT ANY WARRANTY; without even the implied warranty of nathan@0: * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the nathan@0: * GNU General Public License for more details. nathan@0: * nathan@0: * You should have received a copy of the GNU General Public License nathan@0: * along with this program; if not, write to the Free Software nathan@0: * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. nathan@0: * Or, point your browser to http://www.gnu.org/copyleft/gpl.html nathan@0: */ nathan@0: nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: #include nathan@0: nathan@0: #include "common.h" nathan@0: #include "setup-mp3.h" nathan@0: #include "stream.h" nathan@0: #include "network.h" nathan@29: #include "data.h" nathan@0: #include "menu-async.h" nathan@0: #include "i18n.h" nathan@0: #include "version.h" nathan@0: nathan@0: #define USE_MMAP nathan@0: #define MAX_MMAP_SIZE (32*1024*1024) nathan@0: #define MP3FILE_BUFSIZE (32*1024) nathan@0: nathan@0: #ifdef USE_MMAP nathan@0: #include nathan@0: #endif nathan@0: nathan@0: #define DEFAULT_PORT 80 // default port for streaming (HTTP) nathan@0: nathan@0: //#define DUMP_HEAD "/var/tmp/headers" nathan@0: nathan@0: // --- cIO --------------------------------------------------------------------- nathan@0: nathan@0: #if 0 nathan@0: cIO::cIO(const char *Filename, bool Log) nathan@0: :cFileInfo(Filename) nathan@0: { nathan@0: log=Log; nathan@0: readpos=0; nathan@0: } nathan@0: nathan@0: cIO::~cIO() nathan@0: { nathan@0: } nathan@0: nathan@0: // --- cStreamIO --------------------------------------------------------------- nathan@0: nathan@0: cStreamIO::cStreamIO(void) nathan@0: { nathan@0: data=0; nathan@0: } nathan@0: nathan@0: cStreamIO::~cStreamIO() nathan@0: { nathan@0: StreamClear(true); nathan@0: } nathan@0: nathan@0: void cStreamIO::StreamClear(bool all) nathan@0: { nathan@0: if(all) { free(data); data=0; } nathan@0: fill=0; nathan@0: } nathan@0: nathan@0: unsigned char *cStreamIO::StreamInit(int Size) nathan@0: { nathan@0: StreamClear(true); nathan@0: size=Size; data=(unsigned char *)malloc(size); nathan@0: return data; nathan@0: } nathan@0: nathan@0: int cStreamIO::Stream(const unsigned char *rest) nathan@0: { nathan@0: if(rest && fill) { // copy remaining data to start of buffer nathan@0: fill-=(rest-data); nathan@0: memmove(data,rest,fill); nathan@0: } nathan@0: else fill=0; nathan@0: nathan@0: unsigned long r=Read(data+fill,size-fill); nathan@0: if(r>=0) { nathan@0: fill+=r; nathan@0: return fill; nathan@0: } nathan@0: return -1; nathan@0: } nathan@0: nathan@0: // --- cFileIO ----------------------------------------------------------------- nathan@0: nathan@0: cFileIO::cFileIO(const char *Filename, bool Log) nathan@0: :cIO(Filename,Log) nathan@0: { nathan@0: fd=-1; nathan@0: } nathan@0: nathan@0: cFileIO::~cFileIO() nathan@0: { nathan@0: Close(); nathan@0: } nathan@0: nathan@0: bool cFileIO::Open(void) nathan@0: { nathan@0: if(fd>=0) return Seek(0,SEEK_SET); nathan@0: if(FileInfo(log)) { nathan@0: if((fd=open(Filename,O_RDONLY))>=0) { nathan@0: StreamClear(false); nathan@0: readpos=0; nathan@0: return true; nathan@0: } nathan@0: else if(log) { esyslog("ERROR: open failed on %s: %s",Filename,strerror(errno)); } nathan@0: } nathan@0: Close(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: void cFileIO::Close(void) nathan@0: { nathan@0: if(fd>=0) { close(fd); fd=-1; } nathan@0: } nathan@0: nathan@0: int cFileIO::Read(unsigned char *Data, int Size) nathan@0: { nathan@0: unsigned long r=read(fd,Data,Size); nathan@0: if(r>=0) readpos+=r; nathan@0: else if(log) { esyslog("ERROR: read failed in %s: %s",Filename,strerror(errno)); } nathan@0: return r; nathan@0: } nathan@0: nathan@0: bool cFileIO::Seek(unsigned long long pos, int whence) nathan@0: { nathan@0: if(pos>=0 && pos<=Filesize) { nathan@0: StreamClear(false); nathan@0: if((readpos=lseek64(fd,pos,whence))>=0) { nathan@0: if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); } nathan@0: return true; nathan@0: } nathan@0: else if(log) { esyslog("ERROR: seek failed in %s: %s",Filename,strerror(errno)); } nathan@0: } nathan@0: else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename)) nathan@0: return false; nathan@0: } nathan@0: #endif nathan@0: nathan@0: // --- cStream ----------------------------------------------------------------- nathan@0: nathan@0: cStream::cStream(const char *Filename) nathan@0: :cFileInfo(Filename) nathan@0: { nathan@0: fd=-1; ismmap=false; buffer=0; nathan@0: } nathan@0: nathan@0: cStream::~cStream() nathan@0: { nathan@0: Close(); nathan@0: } nathan@0: nathan@0: bool cStream::Open(bool log) nathan@0: { nathan@0: if(fd>=0) return Seek(); nathan@0: nathan@0: if(FileInfo(log)) { nathan@0: if((fd=open(Filename,O_RDONLY))>=0) { nathan@0: buffpos=readpos=0; fill=0; nathan@0: nathan@0: #ifdef USE_MMAP nathan@0: if(Filesize<=MAX_MMAP_SIZE) { nathan@0: buffer=(unsigned char *)mmap(0,Filesize,PROT_READ,MAP_SHARED,fd,0); nathan@0: if(buffer!=MAP_FAILED) { nathan@0: ismmap=true; nathan@0: return true; nathan@0: } nathan@0: else dsyslog("mmap() failed for %s: %s",Filename,strerror(errno)); nathan@0: } nathan@0: #endif nathan@0: nathan@0: buffer = new unsigned char[MP3FILE_BUFSIZE]; nathan@0: if(buffer) return true; nathan@0: else { esyslog("ERROR: not enough memory for buffer: %s",Filename); } nathan@0: } nathan@0: else if(log) { esyslog("ERROR: failed to open file %s: %s",Filename,strerror(errno)); } nathan@0: } nathan@0: nathan@0: Close(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: void cStream::Close(void) nathan@0: { nathan@0: #ifdef USE_MMAP nathan@0: if(ismmap) { nathan@0: munmap(buffer,Filesize); buffer=0; ismmap=false; nathan@0: } nathan@0: else { nathan@0: #endif nathan@0: delete buffer; buffer=0; nathan@0: #ifdef USE_MMAP nathan@0: } nathan@0: #endif nathan@0: if(fd>=0) { close(fd); fd=-1; } nathan@0: } nathan@0: nathan@0: bool cStream::Seek(unsigned long long pos) nathan@0: { nathan@0: if(fd>=0 && pos>=0 && pos<=Filesize) { nathan@0: buffpos=0; fill=0; nathan@0: if(ismmap) { nathan@0: readpos=pos; nathan@0: return true; nathan@0: } nathan@0: else { nathan@0: if((readpos=lseek64(fd,pos,SEEK_SET))>=0) { nathan@0: if(readpos!=pos) { dsyslog("seek mismatch in %s, wanted %lld, got %lld",Filename,pos,readpos); } nathan@0: return true; nathan@0: } nathan@0: else { esyslog("ERROR: seeking failed in %s: %d,%s",Filename,errno,strerror(errno)); } nathan@0: } nathan@0: } nathan@0: else d(printf("mp3: bad seek call fd=%d pos=%lld name=%s\n",fd,pos,Filename)) nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest) nathan@0: { nathan@0: if(fd>=0) { nathan@0: if(readpos=0) { nathan@0: buffpos=readpos-fill; readpos+=r; fill+=r; nathan@0: data=buffer; len=fill; nathan@0: return true; nathan@0: } nathan@0: else { esyslog("ERROR: read failed in %s: %d,%s",Filename,errno,strerror(errno)); } nathan@0: } nathan@0: } nathan@0: else { nathan@0: len=0; nathan@0: return true; nathan@0: } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: // ----------------------------------------------------------------------------- nathan@0: nathan@0: #ifdef DUMP_HEAD nathan@0: void Dump(const char *name, char *buffer) nathan@0: { nathan@0: FILE *f=fopen(name,"a"); nathan@0: if(f) { nathan@0: fprintf(f,"<<<< %s\n",buffer); nathan@0: /* nathan@0: int n=strlen(buffer); nathan@0: for(int i=0 ; iConnect(MP3Setup.UseProxy ? MP3Setup.ProxyHost:host , MP3Setup.UseProxy ? MP3Setup.ProxyPort:port)) { nathan@0: d(printf("netstream: -> %s",buff)) nathan@0: if(net->Puts(buff)>0) res=GetHTTPResponse(); nathan@0: } nathan@0: nathan@0: if(cc--==1) asyncStatus.Set(0); nathan@0: return res; nathan@0: } nathan@0: nathan@0: bool cNetStream::ParseHeader(const char *buff, const char *name, char **value) nathan@0: { nathan@34: const char *s=index(buff,':'); nathan@0: if(s && !strncasecmp(buff,name,s-buff)) { nathan@0: s=skipspace(s+1); nathan@0: d(printf("netstream: found header '%s' contents '%s'\n",name,s)) nathan@0: free(*value); *value=strdup(s); nathan@0: return true; nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cNetStream::GetHTTPResponse(void) nathan@0: { nathan@0: bool res=false; nathan@0: char buff[1024], text[128], *newurl=0; nathan@0: int code=-1, hcount=0; nathan@0: while(net->Gets(buff,sizeof(buff))>0) { nathan@0: stripspace(buff); nathan@0: #ifdef DUMP_HEAD nathan@0: Dump(DUMP_HEAD,buff); nathan@0: #endif nathan@0: d(printf("netstream: <- %s\n",buff)) nathan@0: hcount++; nathan@0: if(hcount==1) { // parse status line nathan@0: if(sscanf(buff,"%*[^ ] %d %128s",&code,text)!=2) { nathan@0: esyslog("Bad HTTP response '%s' from %s:%d",buff,host,port); nathan@0: goto out; nathan@0: } nathan@0: } nathan@0: else { // parse header lines nathan@0: if(buff[0]==0) { // headers finish if we receive a empty line nathan@0: switch(code) { nathan@0: case 200: // OK nathan@0: res=true; nathan@0: goto out; nathan@0: case 300: // MULTIPLE_CHOICES nathan@0: case 301: // MOVED_PERMANENTLY nathan@0: case 302: // MOVED_TEMPORARILY nathan@0: if(newurl) { nathan@0: if(ParseURL(newurl,true)) res=SendRequest(); nathan@0: } nathan@0: else esyslog("No location header for redirection from %s:%d",host,port); nathan@0: goto out; nathan@0: default: nathan@0: esyslog("Unhandled HTTP response '%d %s' from %s:%d",code,text,host,port); nathan@0: goto out; nathan@0: } nathan@0: } nathan@0: nathan@0: ParseHeader(buff,"Location",&newurl); nathan@0: ParseHeader(buff,"icy-name",&icyName); nathan@0: ParseHeader(buff,"icy-url",&icyUrl); nathan@0: char *meta=0; nathan@0: if(ParseHeader(buff,"icy-metaint",&meta)) { nathan@0: metaInt=metaCnt=atol(meta); nathan@0: d(printf("netstream: meta interval set to %d\n",metaInt)); nathan@0: } nathan@0: free(meta); nathan@0: } nathan@0: } nathan@0: out: nathan@0: free(newurl); nathan@0: return res; nathan@0: } nathan@0: nathan@0: bool cNetStream::Open(bool log) nathan@0: { nathan@0: if(net && net->Connected()) return true; nathan@0: nathan@0: if(!net) net=new cNet(0,0,0); nathan@0: net->Disconnect(); nathan@0: nathan@0: if(ParseURLFile(Filename,log)) { nathan@0: buffpos=readpos=0; fill=0; nathan@0: buffer = new unsigned char[MP3FILE_BUFSIZE]; nathan@0: if(buffer) { nathan@0: if(SendRequest()) { nathan@0: return true; nathan@0: } nathan@0: } nathan@0: else esyslog("Not enough memory for buffer"); nathan@0: } nathan@0: nathan@0: Close(); nathan@0: return false; nathan@0: } nathan@0: nathan@0: void cNetStream::Close(void) nathan@0: { nathan@0: delete buffer; buffer=0; nathan@0: delete net; net=0; nathan@0: } nathan@0: nathan@0: bool cNetStream::Seek(unsigned long long pos) nathan@0: { nathan@0: return false; nathan@0: } nathan@0: nathan@0: bool cNetStream::Stream(unsigned char * &data, unsigned long &len, const unsigned char *rest) nathan@0: { nathan@0: if(net && net->Connected()) { nathan@0: if(rest && fill) { // copy remaining data to start of buffer nathan@0: fill-=(rest-buffer); // remaing bytes nathan@0: memmove(buffer,rest,fill); nathan@0: } nathan@0: else fill=0; nathan@0: nathan@0: int r=MP3FILE_BUFSIZE-fill; nathan@0: if(metaInt && r>metaCnt) r=metaCnt; nathan@0: r=net->Read(buffer+fill,r); nathan@0: if(r>=0) { nathan@0: fill+=r; data=buffer; len=fill; nathan@0: metaCnt-=r; nathan@0: if(metaInt && metaCnt<=0) { nathan@0: ParseMetaData(); nathan@0: metaCnt=metaInt; nathan@0: } nathan@0: return true; nathan@0: } nathan@0: } nathan@0: return false; nathan@0: } nathan@0: nathan@34: char *cNetStream::ParseMetaString(char *buff, const char *name, char **value) nathan@0: { nathan@0: char *s=index(buff,'='); nathan@0: if(s && !strncasecmp(buff,name,s-buff)) { nathan@0: char *end=index(s+2,'\''); nathan@0: if(s[1]=='\'' && end) { nathan@0: *end=0; nathan@0: s=stripspace(skipspace(s+2)); nathan@0: if(strlen(s)>0) { nathan@0: d(printf("netstream: found metadata '%s' contents '%s'\n",name,s)) nathan@0: free(*value); *value=strdup(s); nathan@0: } nathan@0: //else d(printf("netstream: found empty metadata '%s'\n",name)) nathan@0: return end+1; nathan@0: } nathan@0: else d(printf("netstream: bad metadata format\n")) nathan@0: } nathan@0: return 0; nathan@0: } nathan@0: nathan@0: bool cNetStream::ParseMetaData(void) nathan@0: { nathan@0: unsigned char byte; nathan@0: int r=net->Read(&byte,1); nathan@0: if(r<=0) return false; nathan@0: int metalen=byte*16; nathan@0: if(metalen>0) { nathan@0: char data[metalen+1]; nathan@0: data[metalen]=0; nathan@0: int cnt=0; nathan@0: do { nathan@0: r=net->Read((unsigned char *)data+cnt,metalen-cnt); nathan@0: if(r<=0) return false; nathan@0: cnt+=r; nathan@0: } while(cnt