]> git.vomp.tv Git - vompclient-marten.git/commitdiff
Media Player From Andreas Vogel
authorChris Tallon <chris@vomp.tv>
Sun, 15 Jul 2007 16:56:59 +0000 (16:56 +0000)
committerChris Tallon <chris@vomp.tv>
Sun, 15 Jul 2007 16:56:59 +0000 (16:56 +0000)
29 files changed:
audio.h
audioplayer.cc [new file with mode: 0644]
audioplayer.h [new file with mode: 0644]
demuxeraudio.cc [new file with mode: 0644]
demuxeraudio.h [new file with mode: 0644]
id3.h [new file with mode: 0644]
language-data.h
mark.h
media.cc [new file with mode: 0644]
media.h [new file with mode: 0644]
objects.mk
player.cc
playerradio.cc
readme_media.txt [new file with mode: 0644]
remote.cc
vaudioplayer.cc [new file with mode: 0644]
vaudioplayer.h [new file with mode: 0644]
vdr.cc
vdr.h
vmedialist.cc [new file with mode: 0644]
vmedialist.h [new file with mode: 0644]
vpicture.cc [new file with mode: 0644]
vpicture.h [new file with mode: 0644]
vpicturebanner.cc [new file with mode: 0644]
vpicturebanner.h [new file with mode: 0644]
vwelcome.cc
vwelcome.h
wjpeg.cc
wjpeg.h

diff --git a/audio.h b/audio.h
index 3eba64269da8da8705252d1ad81e3fbca8276b3d..340110c861c239c8edc60b5933f592fdb87ab469 100644 (file)
--- a/audio.h
+++ b/audio.h
@@ -70,6 +70,7 @@ class Audio : public DrainTarget
     // Audio stream type  // FIXME these are MVP specific (probably!)
     const static UCHAR MPEG2_PES = 2;
     const static UCHAR MPEG1_PES = 3; // unused
+    const static UCHAR MP3 = 0;  //media player
 
 #ifdef DEV
     virtual int test()=0;
diff --git a/audioplayer.cc b/audioplayer.cc
new file mode 100644 (file)
index 0000000..0524362
--- /dev/null
@@ -0,0 +1,734 @@
+/*
+    Copyright 2004-2006 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "audioplayer.h"
+#include "vaudioplayer.h"
+#include "demuxeraudio.h"
+#include "timers.h"
+
+AudioPlayer * AudioPlayer::instance=NULL;
+
+AudioPlayer * AudioPlayer::getInstance(View * parent,bool create) {
+//     Log::getInstance()->log("AudioPlayer",Log::DEBUG,"getInstance for view %p, instance=%p",parent,instance);
+       AudioPlayer *np=instance;
+       if (! np && ! create) return NULL;
+       if (! np) {
+               np=new AudioPlayer(parent);
+               np->run();
+               instance=np;
+       }
+       instance->threadLock();
+       instance->frontend=parent;
+       instance->threadUnlock();
+       return instance;
+}
+
+AudioPlayer::AudioPlayer(View *parent) : afeed(this)
+{
+       frontend=parent;
+  audio = Audio::getInstance();
+  logger = Log::getInstance();
+  vdr = VDR::getInstance();
+  logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorI");
+  running=true;
+       playerRunnig=false;
+  streampos = 0;
+       bytesWritten=0;
+  state = S_STOP;
+       requestState=S_STOP;
+       feederState=FEEDER_STOP;
+
+  threadBuffer = NULL;
+  
+       Video::getInstance()->turnVideoOff();
+       requestedSequence=0;
+       sequence=0;
+       playSequence=0;
+       currentPlaySequence=-1;
+       filename=NULL;
+       demuxer=new DemuxerAudio();
+  logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorII");
+       if (!demuxer->init(this, audio, NULL, 0, DemuxerAudio::PACKET_SIZE+200))
+  {
+    logger->log("AudioPlayer", Log::ERR, "Demuxer failed to init");
+    shutdown();
+    return ;
+  }
+  logger->log("AudioPlayer", Log::DEBUG, "Audio player ctorIII");
+  afeed.init();
+       audio->reset();
+       lenInBytes=0;
+  logger->log("AudioPlayer", Log::DEBUG, "Audio player created");
+}
+
+AudioPlayer::~AudioPlayer()
+{
+  if (threadBuffer) free(threadBuffer);
+       Timers::getInstance()->cancelTimer(this,1);
+       controlFeeder(FEEDER_STOP);
+       audio->reset();
+       audio->setStreamType(Audio::MPEG2_PES);
+       delete demuxer;
+       demuxer=NULL;
+       delete filename;
+}
+
+void AudioPlayer::controlFeeder(int feederAction) {
+       logger->log("AudioPlayer",Log::DEBUG,"control feeder old=%d, new=%d",feederState,feederAction);
+       switch (feederAction) {
+               case FEEDER_START:
+               case FEEDER_UNPAUSE:
+                       if (feederState == FEEDER_STOP) 
+                               afeed.start();
+                       afeed.enable();
+                       break;
+               case FEEDER_STOP:
+                       if (feederState != FEEDER_STOP)
+                               afeed.stop();
+                       break;
+               case FEEDER_PAUSE:
+                       afeed.disable();
+                       break;
+       }
+       feederState=feederAction;
+}
+
+
+void AudioPlayer::run() {
+       if (playerRunnig) return;
+       playerRunnig=true;
+       threadStart();
+}
+
+
+void AudioPlayer::shutdown()
+{
+       running=false;
+       if (playerRunnig) {
+               threadSignalNoLock();
+               //wait for the player thread to stop
+               logger->log("AudioPlayer",Log::DEBUG,"shutdown - warting for player thread to stop");
+               //at most wait 10s
+               for (int loopcount=200;playerRunnig && loopcount > 0;loopcount--) {
+                       MILLISLEEP(50);
+               }
+               if (playerRunnig) {
+                 logger->log("AudioPlayer",Log::ERR,"shutdown - unable to stop player within 10s");
+               }
+       }
+       instance=NULL;
+       delete this;
+}
+
+bool AudioPlayer::isPlayerRunning() {
+       return playerRunnig;
+}
+
+int AudioPlayer::setRequestedState(UCHAR rstate) {
+       int rt=0;
+       threadLock();
+       requestState=rstate;
+       requestedSequence++;
+       rt=requestedSequence;
+       threadUnlock();
+       return rt;
+}
+
+
+UCHAR AudioPlayer::getState() {
+       UCHAR rt=0;
+       threadLock();
+       rt=state;
+       threadUnlock();
+       return rt;
+}
+
+void AudioPlayer::setState(UCHAR s) {
+       threadLock();
+       state=s;
+       threadUnlock();
+}
+
+//------------------- externally called functions --------------------------
+int AudioPlayer::play(const char * fn)
+{
+       logger->log("AudioPlayer", Log::DEBUG, "play request for %s", fn);
+       int rt=0;
+       threadLock();
+       if (filename) delete filename;
+       filename=new char[strlen(fn)+1];
+       strcpy(filename,fn);
+       requestState=S_PLAY;
+       requestedSequence++;
+       rt=requestedSequence;
+       playSequence++;
+       threadUnlock();
+       threadSignalNoLock();
+       return rt;
+}
+
+int AudioPlayer::stop()
+{
+       int rt= setRequestedState(S_STOP);
+       threadSignalNoLock();
+       return rt;
+}
+
+int AudioPlayer::pause()
+{
+       int rt= setRequestedState(S_PAUSE);
+       threadSignalNoLock();
+       return rt;
+}
+int AudioPlayer::unpause()
+{
+       int rt= setRequestedState(S_PLAY);
+       threadSignalNoLock();
+       return rt;
+}
+
+int AudioPlayer::fastForward(){
+       int rt=setRequestedState(S_FF);
+       threadSignalNoLock();
+       return rt;
+}
+
+int AudioPlayer::fastBackward(){
+       int rt=setRequestedState(S_BACK);
+       threadSignalNoLock();
+       return rt;
+}
+int AudioPlayer::jumpToPercent(double percent){
+       threadLock();
+       ULONG fsec=demuxer->getSecondsFromLen(lenInBytes);
+       ULONG npos=streampos;
+       if (fsec != 0) {
+               fsec=(ULONG)(((double)fsec*percent)/(double)100);
+               npos=demuxer->positionFromSeconds(fsec);
+               logger->log("AudioPlayer",Log::DEBUG,"new pos %ld from demux",npos);
+       }
+       if (npos == 0) {
+               //the demuxer cannot help us
+               npos=(ULONG)(((double)lenInBytes*percent)/(double)100);
+               logger->log("AudioPlayer",Log::DEBUG,"new pos %ld without demux",npos);
+       }
+       if (npos > lenInBytes) npos=lenInBytes-1;
+       requestedStreampos=npos;
+       requestState=S_POSITION;
+       requestedSequence++;
+       threadUnlock();
+       //no need to wait here...
+       return 0;
+}
+
+int AudioPlayer::skipForward(int seconds) {
+       threadLock();
+       ULONG curr=demuxer->getSecondsFromLen(streampos);
+       ULONG dest=demuxer->positionFromSeconds(curr+(UINT)seconds);
+       if (dest != 0) {
+               logger->log("AudioPlayer",Log::DEBUG,"new pos %ld  skip %ds",dest,seconds);
+               requestedStreampos=dest;
+       }
+       requestState=S_POSITION;
+       requestedSequence++;
+       threadUnlock();
+       return 0;
+}
+int AudioPlayer::skipBackward(int seconds) {
+       threadLock();
+       ULONG curr=demuxer->getSecondsFromLen(streampos);
+       if (curr > (UINT)seconds) {
+               ULONG dest=demuxer->positionFromSeconds(curr-(UINT)seconds);
+               if (dest != 0) {
+                       logger->log("AudioPlayer",Log::DEBUG,"new pos %ld  skip %ds",dest,seconds);
+                       requestedStreampos=dest;
+                       requestState=S_POSITION;
+                       requestedSequence++;
+               }
+       }
+       threadUnlock();
+       return 0;
+}
+
+
+
+// ----------------------------------- Internal functions
+
+
+void AudioPlayer::sendFrontendMessage(ULONG para)
+{
+  logger->log("AudioPlayer", Log::DEBUG, "sending frontend message %ld",para);
+  Message* m = new Message();
+       threadLock();
+  m->to = frontend;
+       threadUnlock();
+  m->from = this;
+  m->message = Message::PLAYER_EVENT;
+  m->parameter = para;
+       Command::getInstance()->postMessageFromOuterSpace(m);
+}
+
+void AudioPlayer::handleVDRerror(){
+                       if (!vdr->isConnected())
+                       {
+                               logger->log("AudioPlayer", Log::ERR, "disconnect");
+                               sendFrontendMessage(CONNECTION_LOST);
+                               setState(S_ERROR);
+                       }
+}
+//open a new file
+//called within the thread!
+int AudioPlayer::openFile() {
+         char * fn=NULL;
+               threadLock();
+               if (filename) {
+               fn=new char[strlen(filename)+1];
+               strcpy(fn,filename);
+               }
+               threadUnlock();
+    demuxer->reset();
+         streampos=0;
+               bytesWritten=0;
+    lenInBytes=vdr->loadImage(fn,0,0);
+    Log::getInstance()->log("Audioplayer", Log::DEBUG, "request file rt=%d file=%s",lenInBytes,fn);
+    handleVDRerror();
+               if (lenInBytes <= 0) {
+                       return 1;
+               }
+    UINT rsize=0;
+    UCHAR *idbuf=vdr->getImageBlock(0,demuxer->headerBytes(),&rsize);
+    handleVDRerror();
+    if (rsize < demuxer->headerBytes() || idbuf == NULL) {
+      if (idbuf) free(idbuf);
+      Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get header for file %s",fn);
+      return 0;
+    }
+    threadLock();
+    int hdrpos=demuxer->checkStart(idbuf,rsize);
+    threadUnlock();
+    if (hdrpos >= 0) {
+      streampos=hdrpos;
+    }
+    if (idbuf) free(idbuf);
+    idbuf=NULL;
+    if (demuxer->getId3Tag() == NULL) {
+      //OK - look at the end
+      idbuf=vdr->getImageBlock(lenInBytes-demuxer->footerBytes(),demuxer->footerBytes(),&rsize);
+      handleVDRerror();
+      if (rsize < demuxer->footerBytes() || idbuf == NULL) {
+        if (idbuf) free(idbuf);
+        Log::getInstance()->log("VAudioplayer", Log::DEBUG, "unable to get footer for file %s",fn);
+        return 0;
+      }
+      threadLock();
+      hdrpos=demuxer->checkID3(idbuf,rsize);
+      threadUnlock();
+      if (hdrpos < 0) {
+        Log::getInstance()->log("VAudioplayer", Log::DEBUG, "no ID3 in footer for file %s",fn);
+      }
+      free(idbuf);
+    }
+               return 0;
+}
+//method called by the playing thread to handle 
+//"commands" by the frontend
+UCHAR AudioPlayer::checkState()
+{
+       threadLock();
+       UCHAR rstate=requestState;
+       UCHAR cstate=state;
+       int rseq=requestedSequence;
+       int cseq=sequence;
+       int fseq=playSequence;
+       threadUnlock();
+       //flag to decide which message to send
+       //to frontend
+       bool newFile=false;
+
+       if ( rseq > cseq) {
+               logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u", cstate, rstate);
+               switch(rstate)
+               {
+                       case S_PAUSE: 
+                               if (cstate != S_PLAY && cstate != S_FF) rstate=cstate; //ignore request
+                               else {
+                                       skipfactor=0;
+                                       demuxer->setSkipFactor(0);
+                                 audio->mute();
+                                 audio->pause();
+                               }
+                               break;
+                       case S_PLAY: // to S_PLAY
+                               skipfactor=0;
+                               demuxer->setSkipFactor(0);
+                               if (fseq != currentPlaySequence || cstate == S_STOP || cstate == S_ERROR || cstate == S_DONE) {
+                                       //this is a new play request interrupting the current
+                     logger->log("AudioPlayer", Log::DEBUG, "replay from start fseq=%d, startseq=%d", fseq, currentPlaySequence);
+                                       threadLock();
+                                       playSequence=fseq;
+                                       threadUnlock();
+                                       currentPlaySequence=fseq;
+                                       newFile=true;
+                                       if (cstate != S_DONE) audio->mute();
+                                       int rt=openFile();
+                                       if (rt != 0) {
+                                               rstate=S_ERROR;
+                                       }
+                                       else {
+                                               audio->unPause();
+                                               if (cstate != S_DONE) {
+                                                       //flush only if we are not coming from stream end
+                                                       thisRead=0;
+                                                       thisWrite=0;
+                                                       if (threadBuffer) free(threadBuffer);
+                                                 threadBuffer=NULL;
+                                                       controlFeeder(FEEDER_STOP);
+                                                       demuxer->flush();
+                                                       controlFeeder(FEEDER_START);
+                                                       audio->reset();
+                                                       audio->systemMuteOff();
+                                                       audio->setStreamType(Audio::MP3); 
+                                               }
+                                               audio->unMute();
+                                               audio->play();
+                                       }
+                               }
+                               else if (cstate == S_PAUSE){
+                                       newFile=true;
+                                       audio->unPause();
+                                       audio->unMute();
+                               }
+                               else if (cstate == S_FF || S_BACK) {
+                                       ;
+                               }
+                               else {
+                                       rstate=cstate;
+                               }
+                               break;
+                       case S_DONE:
+                               Timers::getInstance()->setTimerD(this,1,4);
+                               //inform the frontend
+                               break;
+                       case S_FF:
+                               if (cstate != S_PLAY && cstate != S_PAUSE && cstate != S_FF) {
+                                       rstate=cstate;
+                               }
+                               else {
+                                       if (skipfactor == 0) skipfactor=2;
+                                       else skipfactor=skipfactor<<1;
+                                       if (skipfactor > 16 ) skipfactor=2;
+                                       demuxer->setSkipFactor(skipfactor);
+                               }
+                               if (cstate == S_PAUSE) {
+                                       audio->unPause();
+                                       audio->unMute();
+                               }
+                               break;
+                       case S_POSITION:
+                               if (cstate != S_PLAY && cstate != S_PAUSE) {
+                                       rstate=cstate;
+                               }
+                               else {
+                                       audio->mute();
+                                       audio->unPause();
+                                       controlFeeder(FEEDER_STOP);
+                                       demuxer->flush();
+                                       thisRead=0;
+                                       thisWrite=0;
+                                       if (threadBuffer) free(threadBuffer);
+                                       threadBuffer=NULL;
+                                       streampos=requestedStreampos;
+                                       bytesWritten=streampos;
+                                       audio->reset();
+                                       audio->setStreamType(Audio::MP3); 
+                                       controlFeeder(FEEDER_START);
+                                       audio->unMute();
+                                       audio->play();
+                                       rstate=S_PLAY;
+                               }
+                               break;
+                       default: // to S_STOP
+                               rstate=S_STOP;
+                               audio->mute();
+                               audio->stop();
+                               audio->unPause();
+                               controlFeeder(FEEDER_STOP);
+                               demuxer->flush();
+                               thisRead=0;
+                               thisWrite=0;
+                               if (threadBuffer) free(threadBuffer);
+                               threadBuffer=NULL;
+                   logger->log("AudioPlayer", Log::DEBUG, "stop handled fseq: %d startseq %d completed", playSequence, currentPlaySequence);
+                               break;
+               }
+               threadLock();
+               state=rstate;
+               sequence=rseq;
+               threadUnlock();
+               if (newFile) sendFrontendMessage(NEW_SONG);
+               else if (cstate != rstate && rstate != S_DONE ) {
+                       sendFrontendMessage(STATUS_CHANGE);
+                       //any change after done cancels the "done" timer
+                       Timers::getInstance()->cancelTimer(this,1);
+               }
+               logger->log("AudioPlayer", Log::DEBUG, "Switch state from %u to %u completed nf=%s", cstate, rstate,newFile?"true":"false");
+               //we return the newly set state
+               return rstate;
+       }
+       //rstate could be different but no new request - so return cstate
+       return cstate;
+}
+
+
+
+
+// ----------------------------------- Feed thread
+
+void AudioPlayer::waitTimed(int ms) {
+       threadLock();
+       struct timeval ct;
+       gettimeofday(&ct,NULL);
+       struct timespec nt;
+       int sec=ms/1000;
+       int us=1000*(ms - 1000*sec);
+       nt.tv_sec=ct.tv_sec+sec;
+       nt.tv_nsec=1000*us+1000*ct.tv_usec;
+       threadWaitForSignalTimed(&nt);
+       threadUnlock();
+}
+
+void AudioPlayer::threadMethod()
+{
+       logger->log("AudioPlayer", Log::DEBUG, "player thread started");
+       thisWrite=0;
+       thisRead=0;
+  while(1)
+  {
+               UCHAR cstate=checkState();
+    if (! running) {
+                       break;
+               }
+               if (cstate != S_PLAY && cstate != S_FF && cstate != S_BACK) {
+                       waitTimed(500);
+                       continue;
+               }
+    threadCheckExit();
+
+               if (thisWrite == thisRead) {
+                       //TODO: use normal blocks...
+                       thisRead=0;
+      thisWrite=0;
+                       threadBuffer = vdr->getImageBlock(streampos, BUFLEN , &thisRead);
+      handleVDRerror();
+                       if (!threadBuffer || thisRead == 0) {
+                               //OK we count this as end of stream
+                               //hmm --- we should be able to detect if the audio has gone...
+                               logger->log("AudioPlayer", Log::DEBUG, "stream end");
+                               setRequestedState(S_DONE);
+                               continue;
+                       }
+                       //logger->log("AudioPlayer", Log::DEBUG, "read %ld bytes at pos %ld",thisRead,streampos);
+                       streampos+=thisRead;
+               }
+    
+    threadCheckExit();
+               /*
+    MediaPacket p;
+               memset(&p,sizeof(p),0);
+               p.pos_buffer=0;
+               p.length=thisRead;
+    MediaPacketList pl;
+               pl.push_back(p);
+               audio->PrepareMediaSample(pl,0);
+               UINT bytesWritten=0;
+               UINT rt=audio->DeliverMediaSample(threadBuffer,&bytesWritten);
+               ULONG written=thisRead;
+               if (rt == 0)
+                  written=bytesWritten;
+               */
+    ULONG written= demuxer->put(threadBuffer + thisWrite, thisRead - thisWrite);
+    thisWrite+=written;
+               bytesWritten+=written;
+               if (thisWrite < thisRead) {
+                       if (written == 0) {
+        // demuxer is full and can't take anymore
+                               waitTimed(200);
+                       }
+      }
+               else {
+      //logger->log("AudioPlayer", Log::DEBUG, "block written %d", thisWrite);
+                       thisWrite=0;
+                       thisRead=0;
+      free(threadBuffer);
+      threadBuffer = NULL;
+               }
+
+  }
+
+  logger->log("AudioPlayer", Log::DEBUG, "finished");
+       playerRunnig=false;
+       return;
+}
+
+int AudioPlayer::waitForSequence(int timeout, int seq) {
+       time_t starttime=time(NULL)+timeout;
+       time_t curtime=0;
+  logger->log("AudioPlayer", Log::DEBUG, "waiting for sequence %d",seq);
+       while ((curtime=time(NULL)) < starttime) {
+               int cseq=getSequence();
+               if (cseq >= seq) return cseq;
+               MILLISLEEP(10);
+       }
+       return -1;
+}
+
+int AudioPlayer::getSequence() {
+       int rt=0;
+       threadLock();
+       rt=sequence;
+       threadUnlock();
+       return rt;
+}
+
+
+
+void AudioPlayer::threadPostStopCleanup()
+{
+  if (threadBuffer)
+  {
+    delete(threadBuffer);
+    threadBuffer = NULL;
+  }
+       playerRunnig=false;
+}
+
+void AudioPlayer::call(void *) {
+       threadSignalNoLock();
+}
+
+void AudioPlayer::timercall(int ref) {
+       if (ref == 1) {
+               logger->log("AudioPlayer", Log::DEBUG, "stream end - informing frontend");
+               sendFrontendMessage(STREAM_END);
+       }
+}
+
+//--------------------------- info functions -------------------
+
+char * AudioPlayer::getTitle() {
+       logger->log("AudioPlayer", Log::DEBUG, "getTitle");
+       threadLock();
+       const id3_tag * tag=demuxer->getId3Tag();
+       const char * title=NULL;
+       char *rt=NULL;
+       if (tag != NULL) {
+               title=tag->title;
+       }
+       if (title && strlen(title) != 0) {
+               rt=new char[strlen(title)+1];
+               strcpy(rt,title);
+               rt[strlen(title)]=0;
+       }
+       //let the frontend fill in something
+       threadUnlock();
+       return rt;
+}
+
+char * AudioPlayer::getID3Info() {
+       logger->log("AudioPlayer", Log::DEBUG, "getID3Info");
+       threadLock();
+       int len=0;
+       const id3_tag * tag=demuxer->getId3Tag();
+       int taglen=0;
+       if (tag) taglen=tag->stringlen(false);
+       len+=taglen;
+       const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo();
+       if (info) len+=30;
+       char * rt=NULL;
+       if (len > 0) {
+               char bitrateType='C';
+               if (info && info->avrBitrate != info->bitRate) bitrateType='V';
+               rt=new char[len];
+               if (!tag && info) {
+                       snprintf(rt,len-1,"%s: %s/L%d %cBR,SR=%dk,%s\n",tr("MpegInfo"),
+                                       info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000,
+                                       info->info);
+               }
+               else if (tag && info){
+                       char tmp[taglen+1];
+                       snprintf(rt,len-1,"%s\n"
+                                       "%s: %s/L%d %cBR,SR=%dk,%s\n",
+                                       tag->toString(tmp,taglen,false),
+                                       tr("MpegInfo"),
+                                       info->mpegVersion,info->mpegLayer,bitrateType,info->sampleRate/1000,
+                                       info->info);
+               }
+               else if (tag && !info){
+                       char tmp[taglen+1];
+                       snprintf(rt,len-1,"%s\n",
+                                       tag->toString(tmp,taglen,false));
+               }
+               rt[len-1]=0;
+       }
+       threadUnlock();
+       logger->log("AudioPlayer", Log::DEBUG, "getID3Info returns %s",rt);
+       return rt;
+}
+
+ULONG AudioPlayer::getCurrentTimes(){
+       ULONG rt=0;
+       threadLock();
+       if (streampos != 0){
+         rt=demuxer->getSecondsFromLen(bytesWritten);
+         if (rt == 0) {
+               //we can only guess
+               rt= bytesWritten/DEFAULT_BITRATE;
+               }
+       }
+       threadUnlock();
+       return rt;
+}
+
+ULONG AudioPlayer::getSonglen(){
+       ULONG rt=0;
+       threadLock();
+       const DemuxerAudio::vbrInfo * vbr=demuxer->getVBRINfo();
+       if (vbr) rt=vbr->fileSeconds;
+       else {
+               if (lenInBytes != 0) {
+                       rt=demuxer->getSecondsFromLen(lenInBytes);
+                       if (rt == 0) {
+                               //we can only guess
+                               rt= lenInBytes/DEFAULT_BITRATE;
+                       }
+               }
+       }
+       threadUnlock();
+       return rt;
+}
+
+int AudioPlayer::getCurrentBitrate(){
+       int rt=DEFAULT_BITRATE;
+       threadLock();
+       const DemuxerAudio::mpegInfo *info=demuxer->getMpegInfo();
+       if (info) rt=info->bitRate;
+       threadUnlock();
+       return rt;
+}
diff --git a/audioplayer.h b/audioplayer.h
new file mode 100644 (file)
index 0000000..727e035
--- /dev/null
@@ -0,0 +1,203 @@
+/*
+    Copyright 2004-2006 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef AUDIOPLAYER_H
+#define AUDIOPLAYER_H
+
+#include <stdio.h>
+#include <stdlib.h>
+#ifndef WIN32
+#include <sys/time.h>
+#endif
+#include <time.h>
+
+#include "audio.h"
+#include "remote.h"
+#include "vdr.h"
+#include "callback.h"
+#include "message.h"
+#include "messagequeue.h"
+#include "thread.h"
+#include "afeed.h"
+#include "timerreceiver.h"
+
+#ifdef WIN32
+#include "threadwin.h"
+#else
+#include "threadp.h"
+#endif
+
+
+
+class View;
+class DemuxerAudio;
+
+
+class AudioPlayer : public Thread_TYPE, public Callback, public TimerReceiver
+{
+  public:
+    //the instance method to create the
+    //player instance - only call thie for
+    //the first time in the main thread (no lock!)
+    //later you can change the view the player is connect with
+               //a view that "goes" should call getInstance(NULL,false) to detach
+               //the last media view should shutdown the player
+    static AudioPlayer * getInstance(View * frontend, bool create=true);
+    //start the player thread
+    void run();
+
+    //is the player still running?
+    bool isPlayerRunning();
+
+    void shutdown();
+
+    //each of the commands works as a request
+    //only after getSequence returned the same sequence as those commands this is
+    //handled by the player and getError is valid
+    int play(const char * filename);
+    //stop the player without shutting it down
+    int stop();
+    int pause();
+    int unpause();
+               int fastForward();
+               int fastBackward();
+               int jumpToPercent(double percent);
+               int skipForward(int sec);
+               int skipBackward(int sec);
+
+               //info functions for frontend
+               //delete provided String afterwards
+               //get the played title from ID3 Tag  - or NULL
+               char * getTitle();
+               //get info
+               //multi line String containing ID3 infos
+               char * getID3Info();
+
+    //wait for a particular sequence to be handled
+    //timeout in s, returnes current sequence, -1 on timeout
+    int waitForSequence(int timeout, int sequence);
+    int getSequence();
+
+               //info functions
+
+               //get current position in s
+               ULONG getCurrentTimes();
+               //get song len in s
+               ULONG getSonglen();
+               //current bitrate
+               int getCurrentBitrate();
+               
+    
+    virtual void call(void * caller);
+
+    UCHAR getState() ;
+
+    const static UCHAR S_PLAY = 1;
+    const static UCHAR S_PAUSE = 2;
+    const static UCHAR S_POSITION = 3;
+               //player finished a song - no reset, next will follow
+    const static UCHAR S_DONE=5;
+    const static UCHAR S_STOP = 6;
+    const static UCHAR S_ERROR = 8;
+    const static UCHAR S_FF = 9;
+    const static UCHAR S_BACK = 10;
+
+    //message parameters for frontend messages
+    const static ULONG CONNECTION_LOST=1;
+    const static ULONG STREAM_END=2;
+    const static ULONG STREAM_ERR=3;
+               const static ULONG STATUS_CHANGE=4; //some info has been changed
+               const static ULONG NEW_SONG=5; //some info has been changed
+               const static ULONG SHORT_UPDATE=6; //timer info update
+
+    virtual void timercall(int reference);
+
+  protected:
+    void threadMethod();
+    void threadPostStopCleanup();
+
+  private:
+               //to guess lengthes if the demux does not know
+               const static ULONG DEFAULT_BITRATE=128000;
+    AudioPlayer(View *frontend);
+    virtual ~AudioPlayer();
+    static AudioPlayer * instance;
+    bool playerRunnig;
+    Audio* audio;
+    VDR* vdr;
+    Log* logger;
+
+    DemuxerAudio *demuxer;
+    AFeed afeed;
+
+    //feeder control
+    const static int FEEDER_START=1;
+    const static int FEEDER_STOP=2;
+    const static int FEEDER_PAUSE=3;
+    const static int FEEDER_UNPAUSE=4;
+    void controlFeeder(int action) ;
+    int feederState;
+
+    //synchronized get/set methods for states
+    int setRequestedState(UCHAR st);
+    void setState(UCHAR st);
+
+    //to be called from within the thread
+    UCHAR checkState();
+
+               //variables used by the thread
+               UINT thisWrite;
+         UINT thisRead;
+    bool running;
+
+    UCHAR *threadBuffer;
+    UCHAR state;
+    UCHAR requestState;
+    ULONG streampos;
+    ULONG lenInBytes;
+               ULONG bytesWritten;
+               ULONG requestedStreampos;
+
+               int skipfactor;
+    //the buffer len in bytes
+    const static int BUFLEN=50*1024;
+    View *frontend;
+    //requested sequence
+    int requestedSequence;
+    //handled sequence
+    int sequence;
+    //startplay sequence
+    int currentPlaySequence;
+    //sequence that is changed for each new filename
+    int playSequence;
+
+    char * filename;
+    int openFile();
+    void handleVDRerror();
+
+    void sendFrontendMessage(ULONG para);
+
+    void waitTimed(int ms);
+
+
+};
+
+#endif
+
diff --git a/demuxeraudio.cc b/demuxeraudio.cc
new file mode 100644 (file)
index 0000000..e2f5244
--- /dev/null
@@ -0,0 +1,1223 @@
+/*
+    Copyright 2006 Mark Calderbank, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "demuxeraudio.h"
+#include "audio.h"
+#include "i18n.h"
+
+#define HDRBYTE1 0xff
+#define HDRBYTE2 0xe0
+#define HDRBYTE2MASK 0xe0
+
+
+
+class PacketBuffer {
+  
+  public:
+    PacketBuffer(Stream *as,UCHAR strtype) {
+      log=Log::getInstance();
+      audio=as;
+      streamtype=strtype;
+      newStream();
+    }
+    //just handle the data (do not deal with headers)
+    int putInternal(UCHAR* buf,int len);
+    void reset(){
+      partPacket=0;
+      bytesWritten=0;
+      framelen=DemuxerAudio::PACKET_SIZE;
+    }
+    void newStream() {
+      reset();
+      numpackets=0;
+      numbytes=0;
+                       skipfactor=0;
+                       numskip=0;
+    }
+    bool bufferFull() {
+      return (partPacket>=framelen);
+    }
+    //can we write a new packet?
+    bool bufferEmpty() {
+      return partPacket==0;
+    }
+    //only set this, if buffer empty
+    //otherwise ignored!
+    bool setFramelen(int len) {
+      if (! bufferEmpty() ) return false;
+      if (len > (int)DemuxerAudio::PACKET_SIZE) return false;
+      framelen=len;
+      return true;
+    }
+    //how much bytes do we need to fill the packet?
+    int bytesMissing() {
+      return framelen-partPacket;
+    }
+    int getFramelen() {
+      return framelen;
+    }
+               void setSkipFactor(int factor) {
+                       skipfactor=factor;
+                       numskip=0;
+               }
+  private:
+    void packetWritten() {
+      numbytes+=framelen;
+      //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written packet %ld l=%d, bytes %ld",numpackets,framelen,numbytes);
+      numpackets++;
+      reset();
+    }
+               bool doSkip();
+    UCHAR store[DemuxerAudio::PACKET_SIZE]; // Storage for partial packets
+    int partPacket;    // Length of partial packet stored from previous put()
+    int bytesWritten;  //if they are !=0 and != framelength the stream is full...
+    int framelen;
+    Log * log;
+    Stream * audio;
+    UCHAR streamtype;
+    //global counters
+    ULONG numpackets;
+    ULONG numbytes;
+               int skipfactor;
+               int numskip;
+};
+
+
+DemuxerAudio::DemuxerAudio(int p_vID, int p_aID)
+{
+  inSync=false;
+  isStarting=true;
+  log=Log::getInstance();
+  readHeaders=0;
+  streamtype=Audio::MP3;
+  buffer=new PacketBuffer(&audiostream,streamtype);
+//  buffer=new PacketBuffer(&teststream,streamtype);
+  globalBytesWritten=0;
+  id3=NULL;
+  info=NULL;
+       vbr=NULL;
+  reset();
+}
+
+DemuxerAudio::~DemuxerAudio() {
+  delete buffer;
+  if(info) delete info;
+  if(id3) delete id3;
+       if (vbr) delete vbr;
+}
+
+void DemuxerAudio::flush()
+{
+  Demuxer::flushAudio();
+  buffer->newStream();
+  tmpFill=0;
+}
+
+void DemuxerAudio::reset() {
+  buffer->newStream();
+  tmpFill=0;
+  readHeaders=0;
+  outOfSync=0;
+  globalBytesWritten=0;
+  if (id3) delete id3;
+  id3=NULL;
+  if (info) delete info;
+  info=NULL;
+       if (vbr) delete vbr;
+       vbr=NULL;
+  inSync=false;
+  hasHdrInfo=false;
+  hasVBRInfo=false;
+  isStarting=true;
+  hdrBitrate=128000;
+  hdrSamplingRate=44100;
+  avrBitrate=0;
+  hdrFramelen=0;
+  isStarting=true;
+}
+
+int DemuxerAudio::scan(UCHAR *buf, int len)
+{
+  //no differend pids here
+  return 0;
+}
+
+void DemuxerAudio::setVID(int p_vID)
+{
+}
+
+void DemuxerAudio::setAID(int p_aID)
+{
+}
+
+static char * id3_1_genre[] = {
+  "Blueshhh",
+  "Classic Rock",
+  "Country",
+  "Dance",
+  "Disco",
+  "Funk",
+  "Grunge",
+  "Hip-Hop",
+  "Jazz",
+  "Metal",
+  "New Age",
+  "Oldies",
+  "Other",
+  "Pop",
+  "R&B",
+  "Rap",
+  "Reggae",
+  "Rock",
+  "Techno",
+  "Industrial",
+  "Alternative",
+  "Ska",
+  "Death Metal",
+  "Pranks",
+  "Soundtrack",
+  "Euro-Techno",
+  "Ambient",
+  "Trip-Hop",
+  "Vocal",
+  "Jazz+Funk",
+  "Fusion",
+  "Trance",
+  "Classical",
+  "Instrumental",
+  "Acid",
+  "House",
+  "Game",
+  "Sound Clip",
+  "Gospel",
+  "Noise",
+  "AlternRock",
+  "Bass",
+  "Soul",
+  "Punk",
+  "Space",
+  "Meditative",
+  "Instrumental Pop",
+  "Instrumental Rock",
+  "Ethnic",
+  "Gothic",
+  "Darkwave",
+  "Techno-Industrial",
+  "Electronic",
+  "Pop-Folk",
+  "Eurodance",
+  "Dream",
+  "Southern Rock",
+  "Comedy",
+  "Cult",
+  "Gangsta",
+  "Top 40",
+  "Christian Rap",
+  "Pop/Funk",
+  "Jungle",
+  "Native American",
+  "Cabaret",
+  "New Wave",
+  "Psychadelic",
+  "Rave",
+  "Showtunes",
+  "Trailer",
+  "Lo-Fi",
+  "Tribal",
+  "Acid Punk",
+  "Acid Jazz",
+  "Polka",
+  "Retro",
+  "Musical",
+  "Rock & Roll",
+  "Hard Rock"
+};
+
+
+
+static int bitrateTable[16][5]={
+/*        L1,L2,L3,2L1,2L2 */
+/*0000*/ {-1,-1,-1,-1,-1},
+/*0001*/ {32,32,32,32,8},
+/*0010*/ {64,48,40,48,16},
+/*0011*/ {96,56,48,56,24},
+/*0100*/ {128,64,56,64,32},
+/*0101*/ {160,80,64,80,40},
+/*0110*/ {192,96,80,96,48},
+/*0111*/ {224,112,96,112,56},
+/*1000*/ {256,128,112,128,64},
+/*1001*/ {288,160,128,144,80},
+/*1010*/ {320,192,160,160,96},
+/*1011*/ {352,224,192,176,112},
+/*1100*/ {384,256,224,192,128},
+/*1101*/ {416,320,256,224,144},
+/*1110*/ {448,384,320,256,160},
+/*1111*/ {-1,-1,-1,-1,-1} };
+
+static int  samplingRateTable[4][3]={
+/*00*/ {44100,22050,11025},
+/*01*/ {48000,24000,12000},
+/*10*/ {32000,16000,8000},
+/*11*/ {-1,-1,-1}};
+
+//max 7 char!
+static const char * mpegString(UCHAR code) {
+  switch(code) {
+    case 0:
+      return "MPEG2.5";
+    case 1:
+      return "RESERV";
+    case 2:
+      return "MPEG 2";
+    case 3:
+      return "MPEG 1";
+  }
+  return "UNKNOWN";
+}
+
+static const char * layerString(UCHAR code) {
+  switch(code) {
+    case 0:
+      return "Layer reserved";
+    case 1:
+      return "Layer III";
+    case 2:
+      return "Layer II";
+    case 3:
+      return "Layer I";
+  }
+  return "Layer UNKNOWN";
+}
+/**
+  * parse an id3 Header
+  * provided by Brian Walton
+  * @returns -1 of nothing found
+  */
+  
+int DemuxerAudio::id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame)
+{
+  if (buf[0] < 0x20 || buf[1] < 0x20 || buf [2] < 0x20 ) return -1;
+  frame->size = (buf[4] & 0x7F) << 21 | (buf[5] & 0x7F) << 14 |  (buf[6] & 0x7F) << 7 | (buf[7] & 0x7F);
+  if (frame->size == 0) return -1;
+  //TODO. clearify flags against:
+  //http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b
+  frame->flags.tagAlterPreserv = (buf[8] & 0x80) >> 7;
+  frame->flags.filelterPreserv = (buf[8] & 0x40) >> 6;
+  frame->flags.readOnly = (buf[8] & 0x20) >> 5;
+  frame->flags.groupId = (buf[9] & 0x20) >> 5;
+  frame->flags.compression = (buf[9] & 0x80) >> 7;
+  frame->flags.encryption = (buf[9] & 0x40) >> 6;
+  frame->flags.unsync = 0;
+  frame->flags.dataLen = 0;
+  return 0;
+}
+
+ /**
+  * parse an id3 Header
+  * provided by Brian Walton
+  * @returns -1 of nothing found
+  */
+  
+int DemuxerAudio::id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame)
+{
+  if (buf[0] < 0x20 || buf[1] < 0x20 || buf[2] < 0x20) return -1;
+  frame->size = (buf[3] & 0x7F) << 14 |  (buf[4] & 0x7F) << 7 | (buf[5] & 0x7F);
+  if (frame->size == 0) return -1;
+  return 0;
+}
+
+
+//fill an id3tag from a frame payload
+//http://id3.org/id3v2.3.0#head-697d09c50ed7fa96fb66c6b0a9d93585e2652b0b
+//http://id3.org/id3v2-00
+static struct tagid {
+  const char * bytes;
+  int index;
+} knownFrames[]= {
+  //ID3V2.3
+  {"TIT2",1}, //title
+  {"TPE1",2}, //artist
+  {"TCON",3}, //genre
+  {"TRCK",6}, //track
+  {"TYER",4}, //year
+  {"TALB",5}, //album
+  {"TCOM",7}, //composer
+  {"COMM",8}, //comment
+              //Text encoding           $xx
+              //Language                $xx xx xx
+              //Short content descrip.  <text string according to encoding> $00 (00)
+              //The actual text         <full text string according to encoding>
+  //ID3V2.0
+  {"TT2",1 },
+  {"TP1",2 },
+  {"TCM",7 },
+  {"TCO",3 }, //(genreNumber)
+  {"TAL",5 },
+  {"TRK",6 },
+  {"TYE",4 },
+  {"COM",8 }
+};
+#define NUMKNOWN (sizeof(knownFrames)/sizeof(knownFrames[0]))
+
+/*fill in infos
+  from an ID3 V2.x, V2.3 frame into the tags structure
+  frameData must point to the header
+  framelen is the len without header (10 Bytes for V23, 6 Bytes for v2x)
+  */
+
+#define MAXLEN(tagtype) ((UINT)frameLen<sizeof(tag->tagtype)-1?(UINT)frameLen:sizeof(tag->tagtype)-1)
+bool DemuxerAudio::fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23) {
+  int tl=v23?4:3;
+  int tagIndex=-1;
+  if (tag == NULL) return false;
+  if (frameLen < 2) return false;
+  for (UINT i=0;i< NUMKNOWN;i++) {
+    if(strncmp((char *)frameData,knownFrames[i].bytes,tl) == 0) {
+      tagIndex=knownFrames[i].index;
+      break;
+    }
+  }
+  if (tagIndex < 0) return false;
+  UCHAR encoding=*(frameData+dataOffset);
+  dataOffset++;
+  frameLen--;
+  if (encoding != 0) {
+    log->log("DemuxerAudio",Log::DEBUG,"unknown encoding for tag %d, tagid %s",encoding,
+        knownFrames[tagIndex].bytes);
+    return false;
+  }
+  switch(tagIndex) {
+    case 1:  //title
+      strncpy(tag->title,(char*)(frameData+dataOffset),MAXLEN(title));
+      tag->title[MAXLEN(title)]=0;
+      break;
+    case 2:  //artist
+      strncpy(tag->artist,(char*)(frameData+dataOffset),MAXLEN(artist));
+      tag->artist[MAXLEN(artist)]=0;
+      break;
+    case 3:  //genre
+      {
+      UCHAR * st=frameData+dataOffset;
+      int genre=0;
+      if (*st=='(') {
+        genre=atoi((const char *)(st+1)) && 31;
+        st=(UCHAR *)id3_1_genre[genre];
+      }
+      strncpy(tag->genre,(char*)st,MAXLEN(genre));
+      tag->genre[MAXLEN(genre)]=0;
+      break;
+      }
+    case 4:  //year
+      strncpy(tag->year,(char *)(frameData+dataOffset),MAXLEN(year));
+      tag->year[MAXLEN(year)]=0;
+      break;
+    case 5:  //album
+      strncpy(tag->album,(char *)(frameData+dataOffset),MAXLEN(album));
+      tag->album[MAXLEN(album)]=0;
+      break;
+    case 6:  //track
+      strncpy(tag->track,(char *)(frameData+dataOffset),MAXLEN(track));
+      tag->track[MAXLEN(track)]=0;
+      break;
+    case 7:  //composer
+      strncpy(tag->composer,(char *)(frameData+dataOffset),MAXLEN(composer));
+      tag->composer[MAXLEN(composer)]=0;
+      break;
+    case 8:  //comment
+      strncpy(tag->comment,(char *)(frameData+dataOffset),MAXLEN(comment));
+      tag->comment[MAXLEN(comment)]=0;
+      break;
+    default:
+      return false;
+  }
+
+  return true;
+}
+
+/**
+  * parse an id3 Header
+  * based on code provided by Brian Walton
+  * @returns -1 of nothing found
+  *  otherwise the id3 info is filled
+  */
+
+int DemuxerAudio::parseID3V2(UCHAR *data, int len) {
+  int debug=0;
+  UCHAR * start=data;
+  id3_header id3header;
+  id3_frame id3frame;
+  id3_tag * id3tag=NULL;
+  //len = read(fd, data, 10);
+  if (len < 10) {
+    delete id3tag;
+    return -1;
+  }
+  len-=10;
+  if(data[0]=='I' && data[1]=='D' && data[2]=='3')
+  {
+    id3tag=new id3_tag();
+    id3header.major = data[3];
+    id3header.minor = data[4];
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 V2.%d.%d found\n", id3header.major, id3header.minor);
+    id3header.flags.unsynchronisation = (data[5] & 0x80)>>7;
+    id3header.flags.extended_header = (data[5] & 0x40)>>6;
+    id3header.flags.experimental = (data[5] & 0x20)>>5;
+    id3header.flags.footer = (data[5] & 0x10)>>4;
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Unsynchronisation flag: %d\n", id3header.flags.unsynchronisation);
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header flag: %d\n", id3header.flags.extended_header);
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Experimental indicator flag: %d\n", id3header.flags.experimental);
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Footer present flag: %d\n", id3header.flags.footer);
+    id3header.size = (data[6] & 0x7F) << 21 | (data[7] & 0x7F) << 14 |  (data[8] & 0x7F) << 7 | (data[9] & 0x7F);
+    if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"ID3 Size: %d\n", id3header.size);
+    data=start+10;
+    if (len <= id3header.size) {
+      if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"header size to big %d, only %d bytes available\n",id3header.size,len);
+      delete id3tag;
+      return -1;
+    }
+    if (id3header.flags.extended_header)
+    {
+      int extended_hdr_hdr=4;  //still to be discussed (id3.org...)
+      //read extended header size
+      if (len < extended_hdr_hdr) {
+        if (debug) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended header found but cannot read\n");
+        delete id3tag;
+        return -1;
+      }
+      if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"remaining %d chars after extended hdr hdr\n", len);
+      id3header.extended_header.size = (data[0] & 0x7F) << 21 | (data[1] & 0x7F) << 14 |  (data[2] & 0x7F) << 7 | (data[3] & 0x7F);
+      if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Extended header size: %d\n", id3header.extended_header.size);
+      if (len <= id3header.extended_header.size+extended_hdr_hdr) {
+      if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"extended Header to big, only %d bytes available\n",len);
+        delete id3tag;
+        return -1;
+      }
+      //lseek(fd, id3header.extended_header.size - 6, SEEK_CUR);
+      data+=id3header.extended_header.size+extended_hdr_hdr;
+      len-=id3header.extended_header.size+extended_hdr_hdr;
+    }
+    //set the end of the header
+    UCHAR * eob=start+id3header.size+10;
+    bool readNext=true;
+    while (data < eob && readNext)
+    {
+      //skip over some padding - found this in lame MCDI tag...
+      if (*data == 0) {
+        data++;
+        continue;
+      }
+      readNext=false;
+      switch(id3header.major)
+      {
+        case 2: //ID3 V2.2.x
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.2 frame, %d : %c %c %c\n", data-start,*data,*(data+1),*(data+2));
+          if (data + 6 >= eob)
+          {
+            break;
+          }
+          if (id3_2_2_FrameParse(data, &id3frame) < 0)
+          {
+            break;
+          }
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame size: %d\n", id3frame.size);
+          fillId3Tag(id3tag,data,id3frame.size,6,false);
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + 6 +1);
+          data+=6+id3frame.size;
+          readNext=true;
+          break;
+        case 3: //ID3 V2.3.x
+          {
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"version 2.3 frame, %d : %c %c %c %c\n", data-start,
+              *data,*(data+1),*(data+2),*(data+3));
+          if (data + 10 >= eob)
+          {
+            break;
+          }
+          if (id3_2_3_FrameParse(data, &id3frame) <0)
+          {
+            break;
+          }
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame size: %d\n", id3frame.size);
+          int dataOffset=10;
+          if (id3frame.flags.groupId)
+          {
+            dataOffset++;
+            if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame group: %d\n", data[dataOffset]);
+          }
+          if (id3frame.flags.compression)
+          {
+            if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame compressed: %d\n", id3frame.flags.compression);
+          }
+          if (id3frame.flags.encryption)
+          {
+            dataOffset+=1;
+            if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"Frame encryption method: %d\n", data[dataOffset]);
+          }
+          fillId3Tag(id3tag,data,id3frame.size-dataOffset+10,dataOffset,true);
+          if (debug != 0) log->log("DemuxerAudio::parseID3V2",Log::DEBUG,"frame payload: %s\n", data + dataOffset +1);
+          data+=10+id3frame.size;
+          readNext=true;
+          break;
+          }
+        default:
+          //don't support this version
+          delete id3tag;
+          return -1;
+      }
+    }
+
+    data=eob;
+    //store the found tag
+    if (id3) delete id3;
+    id3=id3tag;
+    return data-start;
+  }
+  return -1;
+}
+
+/**
+  * parse an id3v1 Header
+  * based on code provided by Brian Walton
+  * @returns -1 of nothing found
+  *  otherwise the id3 info is filled
+  */
+#define MEMCPY(type,len,offset) {int type##max=sizeof(tag->type)-1<len?sizeof(tag->type)-1:len;strncpy(tag->type,(char*)&data[offset],type##max);tag->type[type##max]=0;}
+
+int DemuxerAudio::parseID3V1(UCHAR *data, int len) {
+  int debug=1;
+  if (len < 128) return -1;
+  if(data[0]=='T' && data[1]=='A' && data[2]=='G')
+  {
+    id3_tag * tag=new id3_tag();
+    if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1 tag found\n");
+    MEMCPY(title,30,3);
+    MEMCPY(artist,30,33);
+    MEMCPY(album,30,63);
+    MEMCPY(year,4,93);
+    if (data[125]==0 && data[126]!=0)
+    { //ID3 V1.1
+      if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.1 tag\n");
+      MEMCPY(comment,29,97);
+      sprintf(tag->track, "%d", data[126]);
+    } else {
+      if (debug != 0)log->log("DemuxerAudio::parseID3V1",Log::DEBUG,"ID3 V1.0 tag\n");
+      MEMCPY(comment,30,97);
+    }
+    if (data[127] < sizeof(id3_1_genre)/sizeof(id3_1_genre[0]))
+    {
+      sprintf(tag->genre, id3_1_genre[data[127]]);
+    }
+    if (id3) delete id3;
+    id3=tag;
+    return 0;
+  }
+  return -1;
+}
+
+//infos from http://www.multiweb.cz/twoinches/MP3inside.htm
+int DemuxerAudio::parseVBR(UCHAR *data, int len) {
+       UCHAR *hdr=findHeader(data,len);
+       //we expect the header exactly here
+       if (hdr != data) return -1;
+       const static char * VBRHDR="Xing";
+       int vbridpos=36;
+  UCHAR mpgtype=(data[1] & 0x18)>>3;
+  UCHAR chmode=(data[2] & 0xc0) >> 6;
+  UCHAR layer=(data[2] & 0x06) >>1;
+       if ( mpgtype == 3 && chmode == 11) vbridpos=21;
+       if ( mpgtype != 3 && chmode != 11) vbridpos=21;
+       if ( mpgtype != 3 && chmode == 11) vbridpos=13;
+       //check for the header ID
+       if (vbridpos+(int)strlen(VBRHDR)+4 >= len) {
+               Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header %d",len);
+               return -1;
+       }
+       for (int i=4;i<vbridpos;i++) {
+               if (data[i] != 0) {
+                 Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"garbage when searching VBR header at pos %d",i);
+                       return -1;
+               }
+       }
+       if ( strncmp((char *)&data[vbridpos],VBRHDR,strlen(VBRHDR)) != 0) {
+               Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"no VBR header at pos %d",vbridpos);
+               return -1;
+       }
+       int framedata=vbridpos+strlen(VBRHDR);
+       int expectedLen=0;
+       //OK we should now have a valid vbr header
+       bool hasFramenum=data[framedata+3] & 1;
+       bool hasBytes=data[framedata+3] & 0x2;
+       bool hasTOC=data[framedata+3] & 0x4;
+       expectedLen+=8;
+       if (hasTOC) expectedLen+=100;
+       if (framedata+expectedLen > len) {
+               Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"frame to short for VBR header data %d, expected %d",
+                               len,framedata+expectedLen);
+               return -1;
+       }
+       if (!hasFramenum || ! hasBytes || ! hasTOC) {
+               //not usefull for us..
+               Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"not all parts in VBR header - ignore");
+               return -1;
+       }
+       framedata+=4;
+       if (vbr) delete vbr;
+       vbr=new vbrInfo();
+       vbr->numFrames=data[framedata] << 24 | data[framedata+1]<<16|
+                       data[framedata+2]<<8 |data[framedata+3];
+       framedata+=4;
+       vbr->numBytes=data[framedata] << 24 | data[framedata+1]<<16|
+                       data[framedata+2]<<8 |data[framedata+3];
+       framedata+=4;
+       for (int ti=0;ti<100;ti++) {
+               vbr->table[ti]=data[framedata+ti];
+       }
+       //compute file size in seconds
+       //should be (#of frames -1) *samplesPerFrame / sampleRate
+       //TODO: difference for Mono?
+       ULONG samplesPerFrame=384; //layer1
+       if (layer != 3) samplesPerFrame=1152;
+       vbr->fileSeconds=(vbr->numFrames-1)*samplesPerFrame/hdrSamplingRate;
+       Log::getInstance()->log("DemuxerAudio::parseVBR",Log::DEBUG,"successfully read VBR %ldbytes, %ld frames, %ldsec",
+                       vbr->numBytes,vbr->numFrames,vbr->fileSeconds);
+       return hdrFramelen;
+}
+
+
+
+
+
+
+
+UCHAR * DemuxerAudio::findHeader(UCHAR *buf, int len, bool writeInfo) {
+  while (len >= 3) //assume hdr+crc
+  {
+    UCHAR pattern=*buf;
+    buf++; len--;
+    if (pattern != HDRBYTE1 ) continue;
+    if ((*buf & HDRBYTE2MASK) != HDRBYTE2) continue;
+    if (readHeader((buf-1),4,writeInfo) != 0) continue;
+    return buf-1;
+  }
+  return NULL;
+}
+
+
+
+int DemuxerAudio::readHeader(UCHAR * hbuf,int len,bool writeInfo) {
+  int curFramelen=0;
+  int curBitrate=0;
+  int curSamplingRate=0;
+  if (*hbuf != HDRBYTE1) return -1;
+  hbuf++;
+  if ((*hbuf & HDRBYTE2MASK) != HDRBYTE2) return -1;
+  UCHAR mpgtype=(*hbuf & 0x18)>>3;
+  if (mpgtype == 1) {
+    log->log("DemuxerAudio",Log::DEBUG,"header invalid mpgtype %s %i %i %i",
+        mpegString(mpgtype),*hbuf,*(hbuf+1),*(hbuf+2));
+    return 1;
+  }
+  UCHAR layer=(*hbuf & 0x06) >>1;
+  //bool hasCRC=!(*hbuf & 1);
+  hbuf++;
+  UCHAR bitrateCode=(*hbuf & 0xf0) >>4;
+  UCHAR samplingCode=(*hbuf & 0x0c) >> 2;
+  bool padding=*hbuf & 0x02;
+  hbuf++;
+  //0 Stereo, 1 JointStereo, 2 Dual, 3 Mono
+  UCHAR chmode=(*hbuf & 0xc0) >> 6;
+  //UCHAR extension=(*hbuf & 0x30) >> 4;
+
+  //layercode: 1-L3, 2-L2, 3-L1
+  //columns 0,1,2 for MPEG1
+  UCHAR bitrateColumn=3-layer;
+  if (bitrateColumn > 2) {
+    log->log("DemuxerAudio",Log::DEBUG,"header invalid layer %s %i %i %i",
+        layerString(layer),*(hbuf-2),*(hbuf-1),*hbuf);
+    return 1;
+  }
+  if (mpgtype != 3) bitrateColumn+=3;
+  if (bitrateColumn>4) bitrateColumn=4;
+  curBitrate=1000*bitrateTable[bitrateCode][bitrateColumn];
+  UCHAR sampleRateColumn=0;
+  if (mpgtype == 10) sampleRateColumn=1;
+  if (mpgtype == 0) sampleRateColumn=2;
+  curSamplingRate=samplingRateTable[samplingCode][sampleRateColumn];
+  if (curSamplingRate < 0 || curBitrate < 0) {
+    log->log("DemuxerAudio",Log::DEBUG,"header invalid rates br=%d sr=%d %i %i %i",
+        curBitrate,curSamplingRate,*(hbuf-2),*(hbuf-1),*hbuf);
+    return 1;
+  }
+  int padbytes=0;
+  if (padding) {
+    if (layer == 3) padbytes=4;
+    else padbytes=1;
+  }
+  if (layer == 3) {
+    //Layer 1
+    //FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
+    curFramelen=(12*curBitrate/curSamplingRate+padbytes) * 4;
+  }
+  else {
+    //Layer 2/3
+    //FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
+    curFramelen=144*curBitrate/curSamplingRate+padbytes;
+  }
+  //the header itself
+  if (curFramelen < 32) {
+  log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d-invalid %i %i %i",
+      readHeaders,mpegString(mpgtype),layerString(layer),
+      curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf);
+      return 1;
+  }
+  if (writeInfo || isStarting){
+  log->log("DemuxerAudio",Log::DEBUG,"read header %ld mpgv=%s lc=%s br=%d sr=%d, fl=%d %i %i %i",
+      readHeaders,mpegString(mpgtype),layerString(layer),
+      curBitrate,curSamplingRate,curFramelen,*(hbuf-2),*(hbuf-1),*hbuf);
+    if (info) delete info;
+    info=new mpegInfo();
+    strcpy(info->mpegVersion,mpegString(mpgtype));
+    info->mpegLayer=4-layer;
+    info->bitRate=curBitrate;
+    info->avrBitrate=curBitrate;
+    info->sampleRate=curSamplingRate;
+    const char *chmodStr=tr("Stereo");
+    switch (chmode) {
+      case 1: chmodStr=tr("JointStero");break;
+      case 2: chmodStr=tr("Dual");break;
+      case 3: chmodStr=tr("Mono");break;
+    }
+    snprintf(info->info,sizeof(info->info)-1,"%s",chmodStr);
+  }
+       if (isStarting) avrBitrate=curBitrate;
+  isStarting=false;
+  readHeaders++;
+  //moving average F=0.005
+  avrBitrate=avrBitrate+((5*(curBitrate-avrBitrate))/1024);
+  hdrBitrate=curBitrate;
+  hdrFramelen=curFramelen;
+  hdrSamplingRate=curSamplingRate;
+       hasHdrInfo=true;
+  return 0;
+}
+
+int DemuxerAudio::findPTS(UCHAR* buf, int len, ULLONG* dest)
+{
+  //we have no PTS number ...
+  *dest=0;
+  return (findHeader(buf,len) != NULL)?1:0;
+}
+
+bool PacketBuffer::doSkip() {
+       if (!bufferFull()) return false;
+       if (skipfactor == 0) return false;
+       numskip++;
+       if (numskip >= skipfactor) {
+               //sent at least always 2 packets
+               if (numskip > skipfactor) numskip=0;
+               return false;
+       }
+       packetWritten();
+       return true;
+}
+       
+// just handle the real stream without dealing with the header
+int PacketBuffer::putInternal(UCHAR * wbuf, int len)
+{
+  if (bufferFull()) {
+               if (doSkip()) return 0;
+    //we are still full - so try to write
+    int sent=audio->put(store+bytesWritten,framelen-bytesWritten,streamtype);
+    //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream (still full) pp=%d, framelen=%d, written=%d",sent,partPacket,framelen, bytesWritten );
+    if (sent < (framelen - bytesWritten)) {
+      //packet still not written
+      bytesWritten+=sent;
+      return 0;
+    }
+    packetWritten();
+    //let the demuxer come back with the rest - need to check header first
+    return 0;
+  }
+  if (partPacket+len >= framelen) {
+    //now we have at least a complete packet
+    int bytesConsumed=framelen-partPacket;
+    memcpy(store+partPacket,wbuf,bytesConsumed);
+    partPacket=framelen;
+    //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"stored packet %ld, length %d (last %d) for stream %p",numpackets,framelen,bytesConsumed,audio );
+               if (doSkip()) return bytesConsumed;
+    int sent=audio->put(store,framelen,streamtype);
+    bytesWritten+=sent;
+    //log->log("DemuxerAudio::PacketBuffer",Log::DEBUG,"written %d bytes to stream",sent );
+    if (bytesWritten < framelen) {
+      //still not completely written
+      return bytesConsumed;
+    }
+    packetWritten();
+    //let the player come back...
+    return bytesConsumed;
+  }
+  //OK packet still not complete
+  if (len == 0) return 0;
+  int bytesConsumed=len;
+  memcpy(store+partPacket,wbuf,bytesConsumed);
+  partPacket+=bytesConsumed;
+  return bytesConsumed;
+}
+
+/**
+  major entry for data from a player
+  the demuxer is either in the state headerSearch (packet written or
+  just at the beginning), writing garbage (inSync=false) or
+  handling data (none set)
+  A header is expected at the first byte after the previous packet -
+  otherwise we switch to garbage where we always search for a header
+  (but anyway provide the data to the underlying device - it's probably
+  more intelligent then we are...
+  We only loose a correct position display.
+  */
+int DemuxerAudio::put(UCHAR* wbuf, int len)
+{
+  //return audiostream.put(wbuf,len,streamtype);
+  int framelen=PACKET_SIZE;
+  UCHAR *hdr;
+  int bytesConsumed=0;
+  int oldBytes=0;
+  if (tmpFill != 0 || (buffer->bufferEmpty() && len < HDRLEN)) {
+    //OK we have to copy everything to the tmp buffer
+    int cp=(UINT)len<(PACKET_SIZE-tmpFill)?(UINT)len:(PACKET_SIZE-tmpFill);
+    memcpy(&tmpBuffer[tmpFill],wbuf,cp);
+    oldBytes=tmpFill;
+    tmpFill+=cp;
+    wbuf=tmpBuffer;
+    len=tmpFill;
+    //if there is no header here and our buffer
+    //is empty - just wait for the next header
+    if (len < HDRLEN && buffer->bufferEmpty()) {
+      log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten);
+      return cp;
+    }
+  }
+  while (bytesConsumed < len ) {
+    if (buffer->bufferFull()) {
+      //if this is the first part of the loop, try to write to the stream
+      if (bytesConsumed == 0) buffer->putInternal(wbuf,0);
+      //if the buffer is full, no need to continue
+      if (buffer->bufferFull()) break;
+    }
+    //either we are in a packet (buffer != full && buffer != empty)
+    //or we are searching a header
+    if (buffer->bufferEmpty()) {
+      if (len-bytesConsumed < HDRLEN) {
+        // we cannot  still search
+        if (tmpFill != 0) {
+          //we are already working at the buffer
+          break;
+        }
+        memcpy(tmpBuffer,wbuf,len-bytesConsumed);
+        tmpFill=len-bytesConsumed;
+        log->log("DemuxerAudio",Log::DEBUG,"len to small for header %d at bytes %ld",len,globalBytesWritten);
+        return len;
+      }
+
+      int lastFramelen=hdrFramelen;
+      //if the header has been valid before and we are searching
+      //it should be here
+      int maxsearch=((len-bytesConsumed) < (int)PACKET_SIZE)?len-bytesConsumed:(int)PACKET_SIZE;
+      hdr=findHeader(wbuf,maxsearch);
+      if (hdr != NULL) {
+        //log->log("DemuxerAudio",Log::DEBUG,"header found at offset %d",hdr-wbuf);
+        }
+      //hdr now points to the new header
+      if (hdr !=  wbuf) {
+        //still at least bytes before the header
+        inSync=false;
+        outOfSync++;
+        int garbageBytes=(hdr!=NULL)?(hdr-wbuf):maxsearch;
+        //we consider the garbage now as an own packet
+        //we can consume at most our buffer
+        //basically the buffer should be empty here (we are
+        //in header search - and we fill up each garbage
+
+        int nframesize=garbageBytes;
+        if (nframesize > (int)PACKET_SIZE) {
+          nframesize=(int)PACKET_SIZE;
+        }
+        if (! buffer->bufferEmpty()) {
+           log->log("DemuxerAudio",Log::WARN,"buffer not empty when having garbage to store");
+           //at the end no big problem, we only write the remaining bytes that we have...
+        }
+        else {
+          buffer->setFramelen(nframesize);
+        }
+        log->log("DemuxerAudio",Log::DEBUG,"garbage found at packet %ld (bytes %ld) of length %d, "
+            "framelen set to %d (last fl=%d)",
+            readHeaders,globalBytesWritten,garbageBytes,buffer->getFramelen(),lastFramelen);
+        //hmm - we assume that he low level driver is more intelligent
+        //and give him the data "as is"
+        int written=buffer->putInternal(wbuf,garbageBytes);
+        globalBytesWritten+=written;
+        bytesConsumed+=written;
+        if (written != garbageBytes || hdr == NULL ) {
+          break;
+        }
+        //OK either all the "garbage" is written  or
+        //we found the next header as expected
+        //continue with the next package
+        wbuf=hdr;
+      }
+      //we have to wait now until the buffer is 
+      //free for the next package
+      if ( ! buffer->bufferEmpty()) return bytesConsumed;
+      //this is the place where we start a new packet
+      framelen=hdrFramelen;
+      if ( !buffer->setFramelen(framelen) ) {
+        log->log("DemuxerAudio",Log::DEBUG,"unable to set framelen should=%d, current=%d",
+            framelen,buffer->getFramelen());
+            }
+      inSync=true;
+    }
+    //now we are surely within a packet
+    int written=buffer->putInternal(wbuf,len-bytesConsumed);
+    //update the status
+    globalBytesWritten+=written;
+    wbuf+=written;
+    bytesConsumed+=written;
+    if (written == 0) {
+      //stream is full
+      break;
+    }
+    //OK - handle the rest
+  }
+  if (bytesConsumed >= oldBytes) {
+    //if we consumed more than the old bytes - OK the buffer
+    //can be thrown away
+    tmpFill=0;
+    return bytesConsumed-oldBytes;
+  }
+  else {
+    //only consumed bytes from the buffer
+    if (bytesConsumed == 0) {
+      //restore the buffer
+      tmpFill=oldBytes;
+      return 0;
+    }
+    //shift bytes in buffer
+    for (int i=0;i<oldBytes-bytesConsumed;i++) {
+      tmpBuffer[i]=tmpBuffer[i+bytesConsumed];
+    }
+    tmpFill=oldBytes-bytesConsumed;
+    return 0;
+  }
+}
+
+//info functions
+const id3_tag * DemuxerAudio::getId3Tag() {
+  return id3;
+
+}
+const DemuxerAudio::mpegInfo * DemuxerAudio::getMpegInfo() {
+  if (! hasHdrInfo) return NULL;
+       info->avrBitrate=avrBitrate;
+       info->bitRate=hdrBitrate;
+  return info;
+}
+
+const DemuxerAudio::vbrInfo * DemuxerAudio::getVBRINfo() {
+       return vbr;
+}
+
+int DemuxerAudio::checkStart(UCHAR *b, int len) {
+  UCHAR *start=b;
+  int id3len=parseID3V2(b,len);
+  if (id3len > 0) {
+    char * str=id3->toString();
+    log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 %d bytes of %d",id3len,len);
+    log->log("DemuxerAudio",Log::DEBUG,"parseID3V2 found:%s",str);
+    delete str;
+    b+=id3len;
+    len-=id3len;
+  }
+       int vbrlen=parseVBR(b,len);
+       if (vbrlen > 0 ) {
+               hasVBRInfo=true;
+               b+=vbrlen;
+               len-=vbrlen;
+       }
+  UCHAR * hdr=findHeader(b,len,true);
+  if (hdr == NULL) return -1;
+  return hdr-start;
+}
+
+int DemuxerAudio::checkID3(UCHAR *b, int len) {
+  if (len != 128) return -1;
+  int rt=parseID3V1(b,len);
+  if (rt >= 0) {
+    char * str=id3->toString();
+    log->log("DemuxerAudio",Log::DEBUG,"parseID3V1 found:%s",str);
+    delete str;
+  }
+  return rt;
+}
+
+bool DemuxerAudio::isSync() {
+  return inSync;
+}
+
+UINT DemuxerAudio::getSyncErrors() {
+  return outOfSync;
+}
+
+ULONG DemuxerAudio::getBytesPerSecond()
+{
+  ULONG bps=hdrBitrate;
+  if (! hasHdrInfo) return 0;
+  if (hdrBitrate != avrBitrate) {
+    //we seem to have vbr
+    if (hasVBRInfo) {
+      //vbr stuff TODO
+      bps=avrBitrate;
+    }
+    else {
+      //some guessing
+      bps=avrBitrate;
+    }
+  }
+  bps=bps/8;
+  return bps;
+}
+
+ULONG DemuxerAudio::getSecondsFromLen(ULONG len) {
+  if (! hasHdrInfo) return 0;
+       if (vbr) {
+               //first find the index where we are between
+               //rough starting point:
+               ULONG idx=100*len/vbr->numBytes;
+               if (idx >= 100) idx=99;
+               ULONG idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+               ULONG pbefore=idxPos;
+               ULONG pafter=idxPos;
+               //OK now we know whether we have to go up or down
+               if (idxPos > len) {
+                       //down
+                       while (idxPos > len && idx > 0) {
+                               pafter=idxPos;
+                               idx--;
+                               idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+                               pbefore=idxPos;
+                       }
+                       //OK we are now before our postion
+               }
+               else {
+                       //up
+                       while (idxPos < len && idx < 100 ) {
+                               pbefore=idxPos;
+                               idx++;
+                               idxPos=(vbr->table[idx]) * vbr->numBytes/256;
+                               pafter=idxPos;
+                       }
+                       //after our position
+                       if (idx > 0) idx --;
+               }
+               //idx is now the index before our position
+               //approximate between the 2 points
+               ULONG idxTime=idx * vbr->fileSeconds/100;
+               if (pafter == pbefore) return idxTime;
+               ULONG rt=idxTime+ (len-pbefore)* vbr->fileSeconds/(100 * (pafter-pbefore)) ;
+               if (rt > vbr -> fileSeconds) return vbr->fileSeconds;
+               if (rt < idxTime) return idxTime;
+               return rt;
+       }
+       else {
+               ULONG bps=getBytesPerSecond();
+               if (bps == 0) return 0;
+               return len/bps;
+       }
+}
+  
+ULONG DemuxerAudio::positionFromSeconds(ULONG seconds) {
+  if (! hasHdrInfo) return 0;
+       if (vbr) {
+               ULONG idx=seconds*100/vbr->fileSeconds;
+               if (idx > 99) idx=99;
+               ULONG idxPos=vbr->table[idx] * vbr->numBytes/256;
+               ULONG idx2=idx;
+               if (idx < 99) idx2++;
+               ULONG nextPos=vbr->table[idx] * vbr->numBytes/256;
+               ULONG rt=idxPos+(nextPos-idxPos) * (seconds - idx * vbr->fileSeconds /256);
+               if (rt < idxPos) return idxPos;
+               if ( rt > vbr->numBytes) return vbr->numBytes;
+               return rt;
+       }
+       else {
+               ULONG bps=getBytesPerSecond();
+               return bps*seconds;
+       }
+}
+
+int id3_tag::stringlen(bool withTitle) const {
+       if (!withTitle)
+   return strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+
+    strlen(track)+strlen(composer)+strlen(comment)+8+3+
+    90; //30 chars for each name...
+       else
+   return strlen(title)+strlen(artist)+strlen(genre)+strlen(year)+strlen(album)+
+    strlen(track)+strlen(composer)+strlen(comment)+8+3+
+    120; //30 chars for each name...
+}
+//create a string out of the id3 tag info
+//delete this string afterwards if you did not provide the buffer
+char * id3_tag::toString(char *b, int len, bool withTitle) const {
+  char *ta=tr("Artist");
+//char *tg=tr("Genre");
+//char *ty=tr("Year");
+  char *tx=tr("Album");
+//char *to=tr("Composer");
+//char *tc=tr("Comment");
+  char *tn=tr("Track");
+  /* in the moment:
+     Title: 
+     Artist:
+     Album: year - name
+     Track:
+     */
+  if (b == NULL) {
+    len=stringlen(withTitle);
+    b=new char[len];
+  }
+       const char * del=" - ";
+       if (strlen(year) == 0) del="";
+       if (withTitle){
+    char *tt=tr("Title");
+               snprintf(b,len-1,"%s: %s\n%s: %s\n%s: %s%s%s\n%s: %s",
+                               tt,title,ta,artist,tx,year,del,album,tn,track);
+       }
+       else {
+               snprintf(b,len-1,"%s: %s\n%s: %s%s%s\n%s: %s",
+                               ta,artist,tx,year,del,album,tn,track);
+       }
+  b[len-1]=0;
+  return b;
+}
+
+void DemuxerAudio::setSkipFactor(int factor) {
+       Log::getInstance()->log("DemuxerAudio",Log::DEBUG,"set skipfactor %d\n",factor);
+       buffer->setSkipFactor(factor);
+}
+
diff --git a/demuxeraudio.h b/demuxeraudio.h
new file mode 100644 (file)
index 0000000..95dbd11
--- /dev/null
@@ -0,0 +1,172 @@
+/*
+    Copyright 2006 Mark Calderbank, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef DEMUXERAUDIO_H
+#define DEMUXERAUDIO_H
+
+#include "demuxer.h"
+#include "defines.h"
+#include "id3.h"
+
+class PacketBuffer;
+class DemuxerAudio : public Demuxer
+{
+  public:
+               typedef struct {
+                       char mpegVersion[8]; //MPEG1/2/2,5
+                       int  mpegLayer;      //1,2,3
+                       int  bitRate; 
+                       int  avrBitrate;
+                       int  sampleRate;
+                       char info[20];       //channel mode,...
+               } mpegInfo;
+
+               typedef struct {
+                       ULONG numFrames;
+                       ULONG numBytes;
+                       ULONG fileSeconds;
+                       UCHAR table[100];
+               } vbrInfo;
+    DemuxerAudio(int p_vID = 0, int p_aID = 0);
+               virtual ~DemuxerAudio();
+    virtual void flush();
+               virtual void reset();
+    virtual int scan(UCHAR* buf, int len);
+    virtual int findPTS(UCHAR* buf, int len, ULLONG* dest);
+    virtual void setVID(int p_vID);
+    virtual void setAID(int p_aID);
+    virtual int put(UCHAR* buf, int len);
+
+               //special functions for the audioplayer
+               bool isSync() ;
+               UINT getSyncErrors();
+
+               //set the skip factor
+               void setSkipFactor(int faktor);
+
+               //how many bytes do we need at the file start
+               //ID3V2, VBR + some spare to detect a first header
+               static UINT headerBytes() {
+                       return 4096;
+               }
+               //how many bytes do we need at the end
+               static UINT footerBytes() {
+                       return 128;
+               }
+               //return a reference to an ID3 tag
+               //if not found (from checkStart or checkID3)
+               //NULL will be returned
+               const id3_tag * getId3Tag();
+               const mpegInfo * getMpegInfo();
+               const vbrInfo * getVBRINfo();
+
+               //check for a valid header at the beginning (and parse all we find)
+               //will return the offset in buffer where the first valid
+               //header was found (with potentially skipping ID3 and VBR)
+               //return -1 if nothing found
+               int checkStart(UCHAR * buffer, int len);
+               //check for ID3V1 (at the end)
+               //return 0 if found
+               int checkID3(UCHAR * buffer, int len);
+
+               //return length if we can do this
+               //otherwise return 0
+               ULONG getSecondsFromLen(ULONG len);
+
+               //position within a file
+               //return 0 if we cannot
+               ULONG positionFromSeconds(ULONG seconds);
+
+               //try to read the iD3 tag value (V2xx) and
+               //fill it into the ID3 store
+               bool fillId3Tag(id3_tag * tag,UCHAR * frameData, int frameLen, int dataOffset, bool v23);
+
+    const static UINT PACKET_SIZE=4096;
+  private:
+               /*max size of a packet
+               see http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
+               assumptions: max br 448kb/s, min sample 16k
+               L2/L3 FrameLengthInBytes = 144 * BitRate / SampleRate + Padding
+               L2/3 28*144+8 -> 4040
+               L1 FrameLengthInBytes = (12 * BitRate / SampleRate + Padding) * 4
+               L1 (336+32)*4 = 1472
+               */
+               Log * log;
+               
+               int readHeader(UCHAR* buf, int len, bool writeInfo=false);
+
+               //info flags for the player
+               bool inSync; //set if we read a valid header
+               UINT outOfSync; //counts each invalid read headers
+               bool hasHdrInfo; //if we have been able to successfully
+                                //parse the first header
+               bool hasVBRInfo; //we have VBR and the necessary info
+               //infos from header
+               int hdrFramelen;
+               int hdrBitrate;
+               int hdrSamplingRate;
+               //a little bit guessing for VBR
+               int avrBitrate;
+
+               int streamtype;
+               bool isStarting;
+               ULONG readHeaders;
+               ULONG globalBytesWritten;
+
+
+               //search for the start of a header
+               //return NULL if none found
+               UCHAR * findHeader(UCHAR * buf, int len, bool writeInfo=false);
+               //we need a buffer to be able to read at least a complete header
+               const static int HDRLEN=4;
+               UCHAR tmpBuffer[PACKET_SIZE+HDRLEN];
+               UINT tmpFill;
+
+               //compute the bytes/second from the BR info
+               //return 0 if not there;
+               ULONG getBytesPerSecond();
+
+               //parse ID3V2 at the beginning
+               //set info if there
+               //return length of header, -1 if not found
+               int parseID3V2(UCHAR *data, int len);
+               int id3_2_3_FrameParse(unsigned char buf[], id3_frame *frame);
+    int id3_2_2_FrameParse(unsigned char buf[], id3_frame *frame);
+
+               //parse ID3V1 at the end
+               //set info if there, return -1 otherwise
+    int parseID3V1(UCHAR *data, int len) ;
+
+               //parse a VBR header and set VBR info
+               //input has to point to firts MPEG header in file
+               //potentially after ID3V2
+               int parseVBR(UCHAR *data, int len);
+
+
+               class PacketBuffer* buffer;
+
+               id3_tag *id3;
+               mpegInfo *info;
+               vbrInfo *vbr;
+
+
+};
+
+#endif
diff --git a/id3.h b/id3.h
new file mode 100644 (file)
index 0000000..f1a9661
--- /dev/null
+++ b/id3.h
@@ -0,0 +1,92 @@
+/*
+    Copyright 2006 Brian Walton , Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef ID3TAGS_H
+#define ID3TAGS_H
+
+typedef struct id3_flags_t
+{
+  int unsynchronisation;
+  int extended_header;
+  int experimental;
+  int footer;
+} id3_flags;
+
+typedef struct extended_header_t
+{
+  int size;
+  int flag_bytes;
+  int flags;
+} extended_header;
+
+typedef struct id3_header_t
+{
+  int major;
+  int minor;
+  id3_flags flags;
+  int size;
+  struct extended_header_t extended_header;
+} id3_header;
+
+typedef struct id3_frame_flags_t
+{
+  int tagAlterPreserv;
+  int filelterPreserv;
+  int readOnly;
+  int groupId;
+  int compression;
+  int encryption;
+  int unsync;
+  int dataLen;
+} id3_frame_flags;
+
+typedef struct id3_frame_t
+{
+  int size;
+  id3_frame_flags flags;
+} id3_frame;
+
+typedef struct id3_tag_t
+{
+       public:
+               id3_tag_t() {
+                       title[0]=0;
+                       artist[0]=0;
+                       genre[0]=0;
+                       year[0]=0;
+                       album[0]=0;
+                       track[0]=0;
+                       composer[0]=0;
+                       comment[0]=0;
+               };
+               int stringlen(bool withTitle=false) const;
+               //write to buffer, delete it afterwards (if you did not provide it)
+               char *toString(char * buf=NULL, int len=0,bool withTitle=false) const;
+               char title[50];
+               char artist[50];
+               char genre[50];
+               char year[5];
+               char album[50];
+               char track[7];
+               char composer[50];
+               char comment[29];
+} id3_tag;
+
+#endif
index d1ddc5172b2e702d53fc138e31cafba95296a9a3..a59dcead4987295b5105fdcab1d17116051f55e5 100644 (file)
@@ -1057,7 +1057,288 @@ const I18n::tI18nPhrase I18n::Phrases[] =
     "",
     "86",
   },
- // End marker.
+       //Media Player
+       //we have a new main menu with this...
+  { "5. MediaPlayer",
+    "5. MedienWiedergabe",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "87",
+  },
+  { "6. Options",
+    "6. Einstellungen",
+    "6. Inställningar",
+    "6. Beállítások",
+    "6. Asetukset",
+    "6. Options",
+    "6. Instellingen",
+    "88",
+  },
+  { "7. Reboot",
+    "7. Neustart",
+    "7. Starta om",
+    "7. Ãšjraindítás",
+    "7. Uudelleenkäynnistys",
+    "7. Redemarrer",
+    "7. Herstarten",
+    "89",
+  },
+  { "Loading",
+    "Laden",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "90",
+  },
+  { "rotate",
+    "Drehen",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "91",
+  },
+  { "No Media found",
+    "Keine Medien Datei gefunden",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "92",
+  },
+  { "no VDR connection",
+    "Keine Verbindung zum VDR",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "93",
+  },
+  { "Directory",
+    "Verzeichnis",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "94",
+  },
+  { "Format(px)",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "95",
+  },
+  { "Filesize",
+    "Dateigröße",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "96",
+  },
+  { "Time",
+    "Zeit",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "97",
+  },
+  { "Rotation",
+    "Drehung",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "98",
+  },
+  { "Scale",
+    "Skalierung",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "99",
+  },
+  { "MpegInfo",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "100",
+  },
+   { "FileName",
+    "Dateiname",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "101",
+  },
+   { "Playlist",
+    "Playlist",  //Wiedergabeliste???
+    "",
+    "",
+    "",
+    "",
+    "",
+    "102",
+  },
+   { "unable to open audio file",
+    "kann die Audio Datei nicht Ã¶ffnen",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "103",
+  },
+   { "Rand",
+    "Zuf."
+    "",
+    "",
+    "",
+    "",
+    "",
+    "104",
+  },
+   { "unable to get media list",
+    "kann die Medienliste nicht laden",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "105",
+  },
+   { "JointStereo",
+    "JointStereo",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "106",
+  },
+   { "Stereo",
+    "Stereo",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "107",
+  },
+   { "Dual",
+    "Dual",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "108",
+  },
+   { "Mono",
+    "Mono",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "109",
+  },
+   { "Artist",
+    "Künstler",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "110",
+  },
+   { "Year",
+    "Jahr",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "111",
+  },
+   { "Genre",
+    "Genre",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "112",
+  },
+   { "Album",
+    "Album",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "113",
+  },
+   { "Title",
+    "Titel",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "114",
+  },
+   { "Track",
+    "Nummer",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "115",
+  },
+   { "Composer",
+    "Komponist",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "116",
+  },
+   { "Comment",
+    "Kommentar",
+    "",
+    "",
+    "",
+    "",
+    "",
+    "117",
+  },
+  // End marker.
   { NULL }
 };
 
diff --git a/mark.h b/mark.h
index 7dbc0d5f38cbdee68e71a6a32da28fef18189d9e..e904f96ffbd124f1b5717f709ae4d7e03ad7787d 100644 (file)
--- a/mark.h
+++ b/mark.h
 #ifndef MARK_H
 #define MARK_H
 
+#include <vector>
 #include <stdio.h>
 
 #include "defines.h"
 
+using namespace std;
 
 class Mark
 {
@@ -34,4 +36,6 @@ class Mark
     int pos;
 };
 
+typedef vector<Mark*> MarkList;
+
 #endif
diff --git a/media.cc b/media.cc
new file mode 100644 (file)
index 0000000..25b1ac5
--- /dev/null
+++ b/media.cc
@@ -0,0 +1,183 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "media.h"
+#include "vdr.h"
+#include <time.h>
+
+Media* Media::recInfoFor = NULL;
+
+
+Media::Media()
+{
+  start = 0;
+  displayName = NULL;
+  fileName = NULL;
+  index = -1;
+  markList = NULL;
+  mediaType=MEDIA_TYPE_UNKNOWN;
+}
+
+Media::~Media()
+{
+  if (displayName) { delete[] displayName; displayName = NULL; }
+  if (fileName) { delete[] fileName; fileName = NULL; }
+  index = -1; // just in case
+
+  if (markList && markList->size())
+  {
+    for(UINT i = 0; i < markList->size(); i++)
+    {
+      delete (*markList)[i];
+    }
+    markList->clear();
+    Log::getInstance()->log("Media", Log::DEBUG, "Media destructor, marks list deleted");
+  }
+
+  if (markList) delete markList;
+}
+
+ULONG Media::getTime() const
+{
+  return start;
+}
+
+const char* Media::getDisplayName() const
+{
+  if (displayName) return displayName;
+  return fileName;
+}
+
+const char* Media::getFileName() const
+{
+  return fileName;
+}
+
+void Media::setTime(ULONG tstartTime)
+{
+  start = tstartTime;
+}
+
+void Media::setMediaType(int mtype)
+{
+  mediaType=mtype;
+}
+
+int Media::getMediaType() const
+{
+  return mediaType;
+}
+
+void Media::setDisplayName(char* tDisplayName)
+{
+  if (displayName) delete[] displayName;
+
+  displayName = new char[strlen(tDisplayName) + 1];
+  if (displayName) strcpy(displayName, tDisplayName);
+}
+
+void Media::setFileName(char* tFileName)
+{
+  if (fileName) delete[] fileName;
+
+  fileName = new char[strlen(tFileName) + 1];
+  if (fileName) strcpy(fileName, tFileName);
+}
+
+char * Media::getTimeString(char * buffer) const {
+  if (! buffer) buffer=new char[TIMEBUFLEN];
+  struct tm ltime;
+  time_t recStartTime = (time_t)getTime();
+  struct tm *btime = localtime_r(&recStartTime,&ltime);
+  if (btime && recStartTime != 0) {
+#ifndef _MSC_VER
+  strftime(buffer,TIMEBUFLEN, "%0g/%0m/%0d %0H:%0M ", btime);
+#else
+  strftime(buffer, TIMEBUFLEN, "%g/%m/%d %H:%M ", btime);
+#endif
+  }
+  else {
+    snprintf(buffer,TIMEBUFLEN,"00/00/00 00:00 ");
+    }
+  return buffer;
+}
+
+void Media::loadMarks()
+{
+  markList = VDR::getInstance()->getMarks(fileName);
+}
+
+
+
+int Media::getPrevMark(int currentFrame)
+{
+  MarkList::reverse_iterator i;
+  Mark* loopMark = NULL;
+
+  if (!markList || !markList->size()) return 0;
+
+  for(i = markList->rbegin(); i != markList->rend(); i++)
+  {
+    loopMark = *i;
+    Log::getInstance()->log("Media", Log::NOTICE, "findprev:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame);
+
+    if (loopMark->pos < currentFrame)
+    {
+      Log::getInstance()->log("Media", Log::NOTICE, "findprev:setting pos %i to jumpframe_target",loopMark->pos);
+      return loopMark->pos;
+    }
+  }
+
+  // No previous mark
+  return 0;
+}
+
+int Media::getNextMark(int currentFrame)
+{
+  MarkList::iterator i;
+  Mark* loopMark = NULL;
+
+  if (!markList || !markList->size()) return 0;
+
+  for(i = markList->begin(); i != markList->end(); i++)
+  {
+    loopMark = *i;
+    Log::getInstance()->log("Media", Log::NOTICE, "findnext:comparing Frame %i with current Frame %i",loopMark->pos,currentFrame);
+
+    if (loopMark->pos > currentFrame)
+    {
+      Log::getInstance()->log("Media", Log::NOTICE, "findnext:setting pos %i to jumpframe_target",loopMark->pos);
+      return loopMark->pos;
+    }
+  }
+
+  // No next mark
+  return 0;
+}
+
+bool Media::hasMarks()
+{
+  return (markList && markList->size());
+}
+
+MarkList* Media::getMarkList()
+{
+  return markList;
+}
diff --git a/media.h b/media.h
new file mode 100644 (file)
index 0000000..994f777
--- /dev/null
+++ b/media.h
@@ -0,0 +1,90 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef MEDIA_H
+#define MEDIA_H
+
+#include <stdio.h>
+#include <string.h>
+#include "defines.h"
+#include "recinfo.h"
+#include "mark.h"
+#include "log.h"
+#include "demuxer.h"
+
+/* media types form a bitmask
+   so you can add them to have > 1*/
+#define MEDIA_TYPE_DIR 1
+#define MEDIA_TYPE_AUDIO 2
+#define MEDIA_TYPE_VIDEO 4
+#define MEDIA_TYPE_PICTURE 8
+#define MEDIA_TYPE_UNKNOWN 0
+
+#define MEDIA_TYPE_ALL (1+2+4+8)
+
+
+
+class Media
+{
+  public:
+    Media();
+    ~Media();
+
+    void setTime(ULONG startTime);
+    void setDisplayName(char* displayName);
+    void setFileName(char* fileName);
+    void setMediaType(int mtype);
+
+    ULONG getTime() const;
+    const char* getDisplayName() const;
+    const char* getFileName() const;
+    //return the time as a string
+    //if the user provides a buffer, this one is used, if NULL
+    //is given a new buffer is allocated
+    //caller must delete the buffer after usage!
+    char * getTimeString(char *buffer) const;
+    //length for the time display buffer
+    const static int TIMEBUFLEN=100;
+    int index;
+    int getMediaType() const;
+
+    void loadMarks();
+    int getPrevMark(int currentFrame);
+    int getNextMark(int currentFrame);
+    bool hasMarks();
+    MarkList* getMarkList();
+
+    
+  private:
+    ULONG start;
+    char* displayName;
+    char* fileName;
+    int mediaType;
+
+    // I only want 1 RecInfo loaded at a time
+    // if (recInfoFor == this) then recInfo is valid
+    // else delete recInfo and reload for this recording
+    static Media* recInfoFor;
+
+    MarkList* markList;
+};
+typedef vector<Media*> MediaList;
+
+#endif
index e9277081f3f80d44085276394db68b5b756841cf..d9eb4c21464191b316b4eb5f27dd1022cdca0ed7 100644 (file)
@@ -15,4 +15,6 @@ OBJECTS1 = command.o log.o tcp.o dsock.o thread.o timers.o i18n.o mutex.o     \
            widget.o wselectlist.o wjpeg.o wsymbol.o wbutton.o                 \
            woptionbox.o wtextbox.o wwss.o                                     \
            fonts/helvB24.o fonts/helvB18.o                                    \
-           remote.o led.o mtd.o video.o audio.o osd.o surface.o
+           remote.o led.o mtd.o video.o audio.o osd.o surface.o               \
+           vmedialist.o media.o vpicture.o vpicturebanner.o                   \
+           vaudioplayer.o audioplayer.o demuxeraudio.o
index b47ef452c8fb78e5885a39df77fb4e5631195058..c0578afd6ed18d465a4109253ecc3e1ca70b40a6 100644 (file)
--- a/player.cc
+++ b/player.cc
@@ -671,6 +671,7 @@ void Player::switchState(UCHAR toState, ULONG jumpFrame)
           startup = true;
 
           audio->reset();
+                                       audio->setStreamType(Audio::MPEG2_PES);
           audio->systemMuteOff();
           video->reset();
           demuxer->reset();
@@ -769,6 +770,7 @@ void Player::restartAtFrame(ULONG newFrame)
   video->stop();
   video->reset();
   audio->reset();
+       audio->setStreamType(Audio::MPEG2_PES);
   demuxer->flush();
   demuxer->seek();
   currentFrameNumber = newFrame;
@@ -913,6 +915,7 @@ void Player::threadMethod()
       // if execution gets to here, threadFeedScan hit the start, go to play mode
       state = S_PLAY;
       audio->reset();
+           audio->setStreamType(Audio::MPEG2_PES);
       demuxer->flush();
       demuxer->seek();
       demuxer->setFrameNum(currentFrameNumber);
index 16c74c0407dc1be3557a2de60adf49e0a7770c12..ee1a7882d924791f4efbc039c7af0e7f3d7f76c3 100644 (file)
@@ -367,6 +367,7 @@ void PlayerRadio::switchState(UCHAR toState, ULONG jumpPacket)
           startup = true;
 
           audio->reset();
+                                       audio->setStreamType(Audio::MPEG2_PES);
           audio->systemMuteOff();
           demuxer->reset();
           if (isRecording)
@@ -438,6 +439,7 @@ void PlayerRadio::restartAtPacket(ULONG newPacket)
   afeed.stop();
   threadStop();
   audio->reset();
+       audio->setStreamType(Audio::MPEG2_PES);
   demuxer->flush();
   currentPacketNumber = newPacket;
   demuxer->setPacketNum(newPacket);
diff --git a/readme_media.txt b/readme_media.txt
new file mode 100644 (file)
index 0000000..65af5e5
--- /dev/null
@@ -0,0 +1,120 @@
+Media PLayer Functions
+Author: Andreas Vogel (andvogel@online.de)
+Version: 2007/03/14
+
+Basics:
+Adding some media player functions to VOMP.
+This should enable VOMP to:
+  - work as a picture viewer (for digicam pictures)
+       - play audio (at least mp3)
+       - play (mpeg) video
+For me the priorities are like above, i.e. Prio 1 for the (jpeg) pictures, afterwards
+audio, later video.
+It requires some extensions on the server side (of course).
+
+First Version (delivered 2007/03/14):
+       Basic picture viewer functions
+       You can add a list of directories to vomp.conf that will be used as base dirs to
+  find media files.
+  Example:
+  [Media]
+  Dir.1=/home/Andreas/pictest
+  Dir.2=/home/video
+  Dir.3=/home/bilder
+  Dir.4=/media
+
+  You can have up to 10 directories there.
+       The recognition of media types is based on extensions today. In the moment it recognizes:
+       JPEG,jpeg,JPG,jpg as Jpeg
+       mp3,MP3           as mp3 (not played yet)
+
+       In the vom main menu a new item is added (5 MediaPlayer).
+       This will open a menu with the configured base dirs (/ if none configured).
+       SELECTOR WINDOW
+       You can traverse through the dirs with the normal keys.
+       When pointing to a media file (currently only jpeg) you can activate the viewer with
+       [OK] (normal view) or [PLAY] (slide show).
+       PICTURE VIEWER
+       When viewing a picture, this gets loaded from the server (currently still a little bit slow), 
+       scaled (jpeg libs allows for 1/2, 1/4 and 1/8 only - but seems to be OK for many photos), and 
+       displayed. With [OK] you can toggle the status line, With [RED] you can rotate the picture clockwise,
+       [GREEN] brings up an info box with some picture data. [PLAY] starts the slideshow, |<- and ->| as
+       well as [UP], [DOWN] travers trough all pictures within the directory.
+       << and >> will slower/fasten the slide show (time getting displayed in the status line). II will
+       pause the slide show [STOP] will pause and reset time to 5s.
+
+Version 2007/03/23
+  Added playing audio files.
+       In the moment playing is (on the server side) limited to mp3 (MP3) files. The client side is 
+       prepared to play all MPEG1 files - although only tested for MP3 CBR/VBR stereo, 44,1kBit.
+       Player control is similar to the other available players (recordings).
+       You can use 0...9 to position by x0 percent in the file. |<- and ->| will skip each 10s back/ forward.
+       In principle >> does also work - but seems not to be very usefull in the moment.
+       [UP] and [DOWN] will move backward/forward in a play list.
+       You can start playing all MP3's in a dir from the media view by pressing [PLAY], a single file by pressing OK.
+  The player window will disappear app. 30s after the last key press.
+       The players parses ID3V1,V2.2,V2.3 (thanks to Walt for the supported code) and displays selected info.
+       It also parses VBR headers ("Xing") and uses this info for display and movements.
+       If  the player cannot parse the files, it tries to play them anyway, it will (for the display) assume 
+       128kBit/s CBR.
+       Currently the player uses the same media stream like the picture viewer -so no parallel viewing and
+       listening is possible (will be changed soon).
+
+       The media view has some minor changes:
+       Added the option to sort also randomly (e.g. for playing audio files).
+       Directories are marked with [ ] in the display, they are always displayed on top.
+
+       Additionally some bug fixes (thanks to muellerph and Walt for reporting them) for error handling.
+
+
+Development Issues
+1. Major Changes
+1.1 Surface
+To be able to handle deeply nested directories, the number of views has been extended to 120. This made
+dynamic surface handling necessary. Therefore I changed the surface member. Box has a virtual
+getSurface method, that gets implemented by Widget and View. The View really has a surface member,
+Widgets have a parent (Box) member and query this for the surface.
+At the View there is a new unmap() method, that releases the surface. The next getSurface will
+acquire a new one and will redraw the view (calling it's draw method). This has to be used with
+care...
+1.2. Jpeg
+The WJpeg has been extended to be able to load arbitrary jpeg data via a reader class. It will
+scale the jpeg if possible to fit onto the screen and will rotate it on demand.
+1.3. VDR and server
+VDR has new commands to get a media list, to initialize picture data transfer and to transfer
+picture blocks (used a new command there for possible parallel action with audio later on...).
+On the server side the media list reading has been added and a simple file reader to retrieve the 
+Jpeg data.
+1.4. extensions
+New classes for Media, MediaList, VMediaList, VPicture, VPicturebanner, VPictureinfo.
+2007/03/23
+New classes for audioplayer, demuxeraudio, vaudioplayer
+
+2. Problems
+2.1. setting up devenv needed some changes...
+2.2. -Wno-format was necessary to allow for %g for the date display
+2.3. merging with today's CVS:
+I encountered big problems with the new timer handling. Dangerous situations:
+- timer retrieved from list, waiting to fire but destination gone (canceled timer),
+       nextTimer will not be NULL there... - so need a add. check
+- cancel called within the timer firing - deadlock today, could delete timer from list..
+OK -I added the patch.
+2007/03/23
+
+3. Open/Next Steps
+For me the next important steps would be:
+Audio Playing - basicly done 2007/03/23
+
+       Performance Improvements
+1. have the transfer being handled in a separate thread (would save 50% on large files)
+2. make a scaling (+pot. decoding) on the server side, could lower the sizes and fit
+   better to the screen (but needs some jpeg stuff on the server - I will try to grep from
+   mediamvp - worked on this already...).
+Picture Functions
+1. Colour handling - we would need some colour profile for appropriate picture
+   viewing...
+4. sorting - multiple sort options for media lists..
+   done 2007/03/23
+
+Translations
+English and German are there...
index e1abbfc1128386e30adf8e8f61a32eaabecb8c79..8da388bde9c38f1b9afd9978b2e4e4eef1625a5f 100644 (file)
--- a/remote.cc
+++ b/remote.cc
@@ -224,6 +224,7 @@ void Remote::InitHWCListwithDefaults()
   translist[RECORD] = RECORD;
   translist[STOP] = STOP;
   translist[PAUSE] = PAUSE;
+  translist[PLAY] = PLAY;
   translist[SKIPBACK] = SKIPBACK;
   translist[SKIPFORWARD] = SKIPFORWARD;
 
diff --git a/vaudioplayer.cc b/vaudioplayer.cc
new file mode 100644 (file)
index 0000000..fbb9829
--- /dev/null
@@ -0,0 +1,594 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "vaudioplayer.h"
+#include "audioplayer.h"
+#include <time.h>
+
+VAudioplayer::VAudioplayer(VMediaList *p)
+{
+  parent=p;
+  Video* video = Video::getInstance();
+  create(video->getScreenWidth(),video->getScreenHeight());
+  banner=NULL;
+  fullname=NULL;
+  filename=NULL;
+  ftime=0;
+  playall=false;
+  currentMedia=NULL;
+  justPlaying=false;
+  numentries=p->getNumEntries(MEDIA_TYPE_AUDIO);
+  bannerTime=0;
+    barBlue.set(0, 0, 150, 150);
+
+}
+
+AudioPlayer * VAudioplayer::getPlayer(bool createIfNeeded)
+{
+  AudioPlayer* rt=AudioPlayer::getInstance(this,false);
+  if (! createIfNeeded && rt == NULL) return NULL;
+  if (rt == NULL) {
+    rt=AudioPlayer::getInstance(this);
+    rt->run();
+  }
+  return rt;
+}
+
+VAudioplayer::~VAudioplayer()
+{
+  if (banner) ViewMan::getInstance()->removeView(banner);
+  if (fullname) delete fullname;
+  if (filename) delete filename;
+  Timers::getInstance()->cancelTimer(this,1);
+  Timers::getInstance()->cancelTimer(this,2);
+  Timers::getInstance()->cancelTimer(this,3);
+  //TODO leave this to medialist and stop only...
+  if (getPlayer(false)) {
+    AudioPlayer::getInstance(NULL,false)->shutdown();
+  }
+}
+  
+
+
+void VAudioplayer::draw()
+{
+  Log::getInstance()->log("VAudioplayer::draw", Log::DEBUG, "p=%p", this);
+}
+
+
+int VAudioplayer::handleCommand(int command)
+{
+  Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "p=%p,cmd=%d", this,command);
+  Timers::getInstance()->cancelTimer(this,1);
+  int rt=1;
+  switch(command)
+  {
+    case Remote::DF_UP:
+    case Remote::UP:
+      play(VMediaList::MV_PREV);
+      ViewMan::getInstance()->updateView(this);
+      rt= 2;
+      break;
+    case Remote::FORWARD:
+                       getPlayer()->fastForward();
+      rt=2;
+      break;
+    case Remote::DF_DOWN:
+    case Remote::DOWN:
+      play(VMediaList::MV_NEXT);
+      ViewMan::getInstance()->updateView(this);
+      rt= 2;
+      break;
+    case Remote::SKIPFORWARD:
+                       getPlayer()->skipForward(10);
+                       rt=2;
+                       break;
+    case Remote::SKIPBACK:
+                       getPlayer()->skipBackward(10);
+                       rt=2;
+                       break;
+    case Remote::REVERSE:
+      rt=2;
+      break;
+               case Remote::ZERO:
+                       getPlayer()->jumpToPercent(0);
+                       rt=2;
+                       break;
+               case Remote::ONE:
+                       getPlayer()->jumpToPercent(10);
+                       rt=2;
+                       break;
+               case Remote::TWO:
+                       getPlayer()->jumpToPercent(20);
+                       rt=2;
+                       break;
+               case Remote::THREE:
+                       getPlayer()->jumpToPercent(30);
+                       rt=2;
+                       break;
+               case Remote::FOUR:
+                       getPlayer()->jumpToPercent(40);
+                       rt=2;
+                       break;
+               case Remote::FIVE:
+                       getPlayer()->jumpToPercent(50);
+                       rt=2;
+                       break;
+               case Remote::SIX:
+                       getPlayer()->jumpToPercent(60);
+                       rt=2;
+                       break;
+               case Remote::SEVEN:
+                       getPlayer()->jumpToPercent(70);
+                       rt=2;
+                       break;
+               case Remote::EIGHT:
+                       getPlayer()->jumpToPercent(80);
+                       rt=2;
+                       break;
+               case Remote::NINE:
+                       getPlayer()->jumpToPercent(90);
+                       rt=2;
+                       break;
+    case Remote::OK:
+    case Remote::GREEN:
+    {
+      if (banner) {
+        destroyBanner();
+        }
+      else showBanner(true);
+      if (getPlayer()->getState() == AudioPlayer::S_ERROR) {
+        if (playall) play(VMediaList::MV_NEXT);
+      }
+      rt= 2;
+    }
+    break;
+    case Remote::PLAY:
+    {
+      getPlayer()->unpause();
+      if (getPlayer()->getState() != AudioPlayer::S_ERROR) justPlaying=true;
+      else if (playall) play(VMediaList::MV_NEXT);
+      rt= 2;
+    }
+    break;
+    case Remote::PAUSE:
+      //playall=false;
+      getPlayer()->pause();
+      justPlaying=false;
+      rt= 2;
+      break;
+    case Remote::STOP:
+      getPlayer()->stop();
+      justPlaying=false;
+      rt= 2;
+      break;
+    case Remote::BACK:
+    {
+      rt= 4;
+    }
+    break;
+  }
+  if (playall) startPlayall();
+       if (rt == 2) retriggerBanner();
+  // stop command getting to any more views
+  Log::getInstance()->log("VAudioplayer::handleCommand", Log::DEBUG, "returns p=%p,cmd=%d, rt=%d", this,command,rt);
+  return rt;
+}
+
+void VAudioplayer::processMessage(Message* m)
+{
+  if (m->message == Message::MOUSE_MOVE)
+  {
+    ;
+  }
+  else if (m->message == Message::MOUSE_LBDOWN)
+  {
+    
+    //check if press is outside this view! then simulate cancel
+    int x=(m->parameter>>16)-getScreenX();
+    int y=(m->parameter&0xFFFF)-getScreenY();
+    if (x<0 || y <0 || x>getWidth() || y>getHeight())
+    {
+      ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+    }
+  }
+  else if (m->message == Message::PLAYER_EVENT) {
+    switch (m->parameter) {
+      case AudioPlayer::STREAM_ERR:
+      case AudioPlayer::STREAM_END:
+        if (playall) play(VMediaList::MV_NEXT);
+        else sendViewMsg(this,true);
+        break;
+      case AudioPlayer::STATUS_CHANGE:
+        updateBanner(false);
+        break;
+      case AudioPlayer::SHORT_UPDATE:
+                               if (banner) {
+                                       drawClocks();
+                                       ViewMan::getInstance()->updateView(banner,&clocksRegion);
+                                       ViewMan::getInstance()->updateView(banner,&barRegion);
+                                       Timers::getInstance()->setTimerD(this, 3, 1);
+                               }
+        break;
+      case AudioPlayer::NEW_SONG:
+        updateBanner(true);
+        break;
+      case AudioPlayer::CONNECTION_LOST:
+        destroyBanner();
+        if (AudioPlayer *player=getPlayer(false)) {
+          player->shutdown();
+          player=NULL;
+        }
+        Command::getInstance()->connectionLost();
+        break;
+    }
+  }
+}
+
+VAudioplayer * VAudioplayer::createPlayer(VMediaList * mparent, bool bplayall) {
+   Log::getInstance()->log("VAudioplayer::createPlayer", Log::DEBUG, "p=%p",
+  mparent);
+   VAudioplayer *vmn=new VAudioplayer(mparent);
+   ViewMan::getInstance()->add(vmn);
+   ViewMan::getInstance()->updateView(vmn);
+   vmn->play();
+   if (bplayall) vmn->startPlayall();
+   ViewMan::getInstance()->updateView(vmn);
+   return vmn;
+}
+
+void VAudioplayer::startPlayall() {
+   playall=true;
+}
+
+void VAudioplayer::play(ULONG move) {
+  currentMedia=parent->getMedia(MEDIA_TYPE_AUDIO,move);
+  mediaError=NULL;
+  const char *fname=currentMedia->getFileName();
+  ftime=currentMedia->getTime();
+  int len=strlen(fname)+2+strlen(parent->getDirname());
+  if (fullname) {
+     delete fullname;
+     fullname=NULL;
+     }
+  fullname=new char[len];
+  sprintf(fullname,"%s/%s",parent->getDirname(),fname);
+  if (filename) delete filename;
+  len=strlen(fname)+1;
+  filename=new char[len];
+  strcpy(filename,fname);
+  Log::getInstance()->log("VAudioplayer::load", Log::DEBUG, "filename=%s,p=%p",
+  fullname,this);
+  VDR* vdr=VDR::getInstance();
+  if (vdr->isConnected()) {
+     Log::getInstance()->log("VAudioplayer", Log::DEBUG, "request file %s",filename);
+     int wseq=getPlayer()->play(fullname);
+     if (getPlayer()->waitForSequence(wseq,2)<0) {
+       mediaError=tr("unable to open audio file");
+     }
+     justPlaying=true;
+     //leave this to the update message from the player:
+     //showBanner();
+    }
+  else {
+    if (AudioPlayer * player=getPlayer(false)) {
+      player->shutdown();
+    }
+    Command::getInstance()->connectionLost();
+    }
+  if (mediaError) {
+    showBanner(true);
+  }
+  Log::getInstance()->log("VAudioplayer", Log::DEBUG, "player started for %s",filename);
+}
+
+void VAudioplayer::retriggerBanner() {
+       if (! banner) return;
+       bannerTime=time(NULL);
+}
+
+void VAudioplayer::showBanner(bool forceNewTime) {
+  //if the loading flag is set,
+  //we are in the main thread - so we can (and must) safely hard destroy/create the banner
+  Timers::getInstance()->cancelTimer(this,1);
+  Timers::getInstance()->cancelTimer(this,3);
+  if (! filename || ! currentMedia) {
+    //hmm...
+    destroyBanner();
+    return;
+    }
+  drawBanner();
+  time_t curtime=time(NULL);
+  if (forceNewTime) retriggerBanner();
+  time_t remainingTime=BANNER_TIME+2-(curtime-bannerTime);
+  if (remainingTime < 2) remainingTime=2;
+  bool playerError=getPlayer()->getState()==AudioPlayer::S_ERROR;
+  if (remainingTime < BANNER_ERROR_TIME+2 && playerError) remainingTime=BANNER_ERROR_TIME+2;
+  Timers::getInstance()->setTimerD(this,1,remainingTime);
+  if (playerError) Timers::getInstance()->setTimerD(this,2,BANNER_ERROR_TIME);
+       Timers::getInstance()->setTimerD(this,3, 1);
+  ViewMan::getInstance()->updateView(banner);
+  }
+
+void VAudioplayer::destroyBanner() {
+  if (banner) {
+    ViewMan::getInstance()->removeView(banner);
+    banner=NULL;
+    }
+  Timers::getInstance()->cancelTimer(this,1);
+  Timers::getInstance()->cancelTimer(this,3);
+}
+void VAudioplayer::updateBanner(bool restart) {
+  time_t currTime=time(NULL);
+  time_t lastBanner=bannerTime;
+  if (currTime - lastBanner > BANNER_TIME){
+    destroyBanner();
+  }
+  if (banner||restart) {
+    showBanner(restart);
+    }
+}
+void VAudioplayer::timercall(int clientref) {
+   Log::getInstance()->log("VAudioplayer::timercall", Log::DEBUG, "id=%d",clientref);
+   switch(clientref)
+   {
+      case 1:
+      case 3:
+        {
+          Message* m = new Message(); 
+          //we misuse PLAYER_EVENT here
+          m->message = Message::PLAYER_EVENT;
+          m->to = this;
+          m->from = this;
+          m->parameter=(clientref==1)?AudioPlayer::STATUS_CHANGE:
+                                               AudioPlayer::SHORT_UPDATE;
+          Command::getInstance()->postMessageFromOuterSpace(m);
+          break;
+        }
+      case 2:
+        {
+          bool stillError=false;
+          if (AudioPlayer * player=getPlayer(false)) {
+            stillError=player->getState()==AudioPlayer::S_ERROR;
+          }
+          if (stillError) {
+            //send a stream end to trigger nex in playlist
+            Message* m = new Message(); 
+            //we misuse PLAYER_EVENT here
+            m->message = Message::PLAYER_EVENT;
+            m->to = this;
+            m->from = this;
+            m->parameter=AudioPlayer::STREAM_END;
+            Command::getInstance()->postMessageFromOuterSpace(m);
+          }
+          break;
+        }
+    }
+   
+  }
+
+void VAudioplayer::sendViewMsg(View *v,bool vdestroy) {
+  Message* m = new Message(); 
+  m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW;
+  m->to = ViewMan::getInstance();
+  m->from = v;
+  m->parameter=(ULONG)v;
+  Command::getInstance()->postMessageFromOuterSpace(m);
+}
+
+View * VAudioplayer::createBannerView(int numlines) {
+  View *rt=new View();
+  UINT height=numlines*30+60;
+  UINT vheight=Video::getInstance()->getScreenHeight();
+  UINT vwidth=Video::getInstance()->getScreenWidth();
+  if (height > vheight-2*BANNER_BOTTOM_MARGIN)
+    height=vheight-2*BANNER_BOTTOM_MARGIN;
+  rt->create(vwidth -2*BANNER_X_MARGIN, height);
+  if (Video::getInstance()->getFormat() == Video::PAL)
+  {
+    rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN);
+  }
+  else
+  {
+    rt->setScreenPos(BANNER_X_MARGIN, vheight-height-BANNER_BOTTOM_MARGIN);
+  }
+
+  rt->setBackgroundColour(Colour::VIEWBACKGROUND);
+  rt->setTitleBarOn(0);
+  //from vvideorec 
+       //set the regions for the closcks and bars on banner
+  barRegion.x = rt->area.w-BARLEN-20;
+  barRegion.y = rt->area.h - 30;   // FIXME, need to be - 1? and below?
+  barRegion.w = rt->area.w-BARLEN+10;
+  barRegion.h = 30;
+
+  clocksRegion.x = 130;
+  clocksRegion.y = barRegion.y + 3;
+  clocksRegion.w = 190;
+  clocksRegion.h = surface->getFontHeight();
+  Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"created banner %p",rt);
+  ViewMan::getInstance()->add(rt);
+  return rt;
+}
+
+
+void VAudioplayer::drawBanner(){
+  Log::getInstance()->log("VAudioPlayer",Log::DEBUG, "draw banner for %p",banner);
+  char *title=NULL;
+  char *playerTitle=getPlayer()->getTitle();
+  if (playerTitle) title=playerTitle;
+  else title=filename;
+  char *pl=tr("Playlist");
+  int num=parent->getNumEntries(MEDIA_TYPE_AUDIO,currentMedia->index);
+  bool playerError=getPlayer()->getState() == AudioPlayer::S_ERROR;
+  //1more line for long dirs
+  int numlines=playall?5:4;
+  int len=0;
+  char *first=NULL;
+  char *playerInfo=NULL;
+  if (playerError) {
+    numlines=3;
+    first=tr("Unable to play audio file");
+    len=strlen(first)+3;
+  }
+  else {
+    playerInfo=getPlayer()->getID3Info();
+    len=strlen(filename)+strlen(pl)+30+strlen(parent->getDirname())+Media::TIMEBUFLEN+100;
+    if (playerInfo) {
+      for (UINT i=0;i<strlen(playerInfo);i++) 
+        if (playerInfo[i] == '\n') numlines++;
+      len+=strlen(playerInfo);
+      first=playerInfo;
+    }
+    else {
+      first="";
+    }
+  }
+  if (numlines != bannerlines) {
+    if (banner) destroyBanner();
+    banner=createBannerView(numlines);
+  }
+  if (! banner) {
+    banner=createBannerView(numlines);
+  }
+  bannerlines=numlines;
+  char buf[len];
+  if (playerError) {
+    snprintf(buf,len,"%s",first);
+  }
+  else {
+    char tbuf[Media::TIMEBUFLEN];
+    if (playall) {
+    snprintf(buf,len,"%s\n"
+        "%s:%s\n"
+        "%s: %d/%d\n"
+        "%s:%s\n"
+        "%s:%s\n",
+        first,
+        tr("FileName"),currentMedia->getFileName(),
+        pl,num,numentries,
+        tr("Directory"),parent->getDirname(),
+        tr("Time"),currentMedia->getTimeString(tbuf));
+    }
+    else {
+    snprintf(buf,len,"%s\n"
+        "%s:%s\n"
+        "%s:%s\n"
+        "%s:%s\n",
+        first,
+        tr("FileName"),currentMedia->getFileName(),
+        tr("Directory"),parent->getDirname(),
+        tr("Time"),currentMedia->getTimeString(tbuf));
+    }
+  }
+  Log::getInstance()->log("VAudioPlayer",Log::DEBUG,"info: (%d)%s",strlen(buf),buf);
+  //now the real drawing functions
+  banner->draw();
+  WSymbol w;
+  w.setSurface(banner->surface);
+  //title
+  banner->rectangle(0, 0, banner->area.w, 30, Colour::TITLEBARBACKGROUND);
+  banner->drawText(title, 5, 5, Colour::LIGHTTEXT);
+  banner->drawPara(buf,5,32,Colour::LIGHTTEXT);
+  int x=10;
+  int ybottom=banner->area.h;
+  banner->rectangle(0, ybottom - barRegion.h, banner->area.w, barRegion.h, Colour::TITLEBARBACKGROUND);
+  bool drawSymbol=true;
+  switch(getPlayer()->getState()) {
+    case AudioPlayer::S_PAUSE:
+      w.nextSymbol = WSymbol::PAUSE;
+      break;
+    case AudioPlayer::S_PLAY:
+      w.nextSymbol = WSymbol::PLAY;
+      break;
+    case AudioPlayer::S_DONE:
+      if (playall) 
+        w.nextSymbol = WSymbol::PLAY;
+      else
+        drawSymbol=false;
+      break;
+    case AudioPlayer::S_BACK:
+      w.nextSymbol = WSymbol::FBWD ;
+      break;
+    case AudioPlayer::S_FF:
+      w.nextSymbol = WSymbol::FFWD ;
+      break;
+    default:
+      drawSymbol=false;
+      break;
+  }
+  if (drawSymbol) {
+    w.setSurfaceOffset(x, ybottom-24);
+    w.draw();
+  }
+  else {
+    //stop symbol
+    banner->rectangle(x, ybottom - 23, 18, 16, Colour::LIGHTTEXT);
+  }
+  x+=30;
+  if (playerInfo) delete playerInfo;
+  if (playerTitle) delete playerTitle;
+       drawClocks();
+}
+
+void VAudioplayer::drawClocks() {
+       if (! banner) return;
+  //draw clocks and bar
+  banner->rectangle(clocksRegion, Colour::TITLEBARBACKGROUND);
+
+  time_t currentSec = (time_t)(getPlayer()->getCurrentTimes());
+  time_t lengthSec=(time_t)(getPlayer()->getSonglen());
+       struct tm cpos;
+       struct tm slen;
+       gmtime_r(&currentSec,&cpos);
+       gmtime_r(&lengthSec,&slen);
+
+  char buffer[100];
+  if (currentSec >= lengthSec)
+  {
+    snprintf(buffer,99, "-:--:-- / -:--:--  %03dk",getPlayer()->getCurrentBitrate()/1000);
+  }
+  else
+  {
+    SNPRINTF(buffer, 99, "%01i:%02i:%02i / %01i:%02i:%02i  %03dk", cpos.tm_hour, cpos.tm_min, cpos.tm_sec, slen.tm_hour, slen.tm_min, slen.tm_sec,
+                               getPlayer()->getCurrentBitrate()/1000);
+               //Log::getInstance()->log("VAudioplayer", Log::DEBUG, buffer);
+  }
+
+  banner->drawText(buffer, clocksRegion.x, clocksRegion.y, Colour::LIGHTTEXT);
+  
+       // Draw progress bar
+  int progBarXbase = 0;
+       int barlen=250;
+
+  banner->rectangle(barRegion.x + progBarXbase, barRegion.y + 3, barlen+10, 24, Colour::LIGHTTEXT);
+  banner->rectangle(barRegion.x + progBarXbase + 2, barRegion.y + 5, barlen+6, 20, barBlue);
+
+  if (currentSec > lengthSec) currentSec=lengthSec;
+  if (lengthSec == 0) return;
+
+  // Draw yellow portion
+  int progressWidth = (barlen+2) * currentSec / lengthSec;
+  banner->rectangle(barRegion.x + progBarXbase + 4, barRegion.y + 7, progressWidth, 16, Colour::SELECTHIGHLIGHT);
+
+}
+
+
diff --git a/vaudioplayer.h b/vaudioplayer.h
new file mode 100644 (file)
index 0000000..9514573
--- /dev/null
@@ -0,0 +1,109 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef VAUDIOPLAYER_H
+#define VAUDIOPLAYER_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "colour.h"
+#include "i18n.h"
+#include "vmedialist.h"
+#include "wjpeg.h"
+
+/**
+  * the audio player frontend
+  * will ineract with the parent List for ff,back, slide show...
+  *
+*/
+class AudioPlayer;
+
+class VAudioplayer : public View,public TimerReceiver
+{
+  public:
+    ~VAudioplayer();
+
+    void processMessage(Message* m);
+    int handleCommand(int command);
+    void draw();
+    void timercall(int clientReference);
+    //factory method
+    //return NULL on error
+    static VAudioplayer *createPlayer(VMediaList * parent, bool startPlayall=false);
+    //show the picture currently selected in the parent
+    //potentially moving to next/previous one
+    void play(ULONG move=VMediaList::MV_NONE);
+
+    //start a playall
+    void startPlayall();
+
+
+  private:
+               const static int BANNER_TIME=30;
+               //show time of an error
+               const static int BANNER_ERROR_TIME=8;
+
+
+               //margin on SCREEN on each side
+               const static int BANNER_X_MARGIN=50;
+               //margin on bottom of screen
+               const static int BANNER_BOTTOM_MARGIN=30;
+               //length of the progress bar
+               const static int BARLEN=250;
+
+    VAudioplayer(VMediaList * plist);
+               void retriggerBanner();
+    void showBanner(bool forceRestart=false);
+    void destroyBanner();
+    void updateBanner(bool restart=false);
+               void drawBanner();
+               void drawClocks();
+               View *createBannerView(int numlines);
+               int bannerlines;
+               //add or destroy a view (banner, info)
+               void sendViewMsg(View *v,bool destroy=false);
+               //get the player - create it if necessary
+               AudioPlayer* getPlayer(bool create=true);
+    VMediaList   *parent;
+    View *banner;
+    char * fullname;
+    char * filename;
+    ULONG ftime;
+    bool playall;
+    char * mediaError;
+    Media * currentMedia;
+               bool justPlaying;
+               int numentries;
+               time_t bannerTime;
+               Region barRegion;
+               Region clocksRegion;
+         Colour barBlue;
+
+};
+
+#endif
diff --git a/vdr.cc b/vdr.cc
index 307f7c35f792114dd6736b9fc5956fe16d94d4a6..c89b746aede8ab047052438f51222c6eb4ecb613 100644 (file)
--- a/vdr.cc
+++ b/vdr.cc
@@ -604,12 +604,22 @@ int VDR::stopStreaming()
   return toReturn;
 }
 
+UCHAR* VDR::getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived)
+{
+  return getBlock(position, maxAmount, amountReceived, VDR_GETIMAGEBLOCK);
+}
+
 UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived)
+{
+  return getBlock(position, maxAmount, amountReceived, VDR_GETBLOCK);
+}
+
+UCHAR* VDR::getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd)
 {
   UCHAR buffer[20];
 
   *(unsigned long*)&buffer[0] = htonl(16);
-  *(unsigned long*)&buffer[4] = htonl(VDR_GETBLOCK);
+  *(unsigned long*)&buffer[4] = htonl(cmd);
   *(ULLONG*)&buffer[8]        = htonll(position);
   *(unsigned long*)&buffer[16] = htonl(maxAmount);
 
@@ -1261,3 +1271,164 @@ void VDR::getChannelPids(Channel* channel)
 
   return ;
 }
+
+/**
+  * media List Request:
+  * 4 length
+  * 4 VDR_GETMEDIALIST
+  * 4 flags (currently unused)
+  * n dirname
+  * n+1 0
+  * Media List response:
+  * 4 length
+  * 4 VDR_
+  * 4 numentries
+  * per entry:
+  * 4 media type
+  * 4 time stamp
+  * 4 flags
+  * 4 strlen (incl. 0 Byte)
+  * string
+  * 0
+*/
+MediaList* VDR::getMediaList(const char* parent,int mediaType)
+{
+  Log::getInstance()->log("VDR", Log::DEBUG, "getMediaList %s,type=%d",
+      (parent?parent:"NULL"), mediaType);
+  unsigned long totalLength = 12;
+  if (parent) totalLength+=strlen(parent) + 1;
+  UCHAR* buffer = new UCHAR[totalLength];
+
+  *(unsigned long*)&buffer[0] = htonl(totalLength - 4);
+  *(unsigned long*)&buffer[4] = htonl(VDR_GETMEDIALIST);
+  //unused flags
+  for (int i=8;i<12;i++) buffer[i]=0;
+  //name
+  if (parent) {
+    strcpy((char*)&buffer[12], parent);
+    buffer[totalLength-1]=0;
+    }
+  MUTEX_LOCK(&mutex);
+  if (!connected) { MUTEX_UNLOCK(&mutex); return 0; }
+
+  unsigned int a = tcp->sendPacket(buffer, totalLength);
+  delete []buffer;
+
+  if (a != totalLength)
+  {
+    disconnect();
+    MUTEX_UNLOCK(&mutex);
+    return NULL;
+  }
+
+  if (!getPacket())
+  {
+    MUTEX_UNLOCK(&mutex);
+    return NULL;
+  }
+
+  if (serverError())
+  {
+    MUTEX_UNLOCK(&mutex);
+    return NULL;
+  }
+  if (packetLength < 12) {
+    Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList packet too short, expected 12, got %d", packetLength);
+    freePacket();
+    MUTEX_UNLOCK(&mutex);
+    return NULL;
+    }
+  MediaList* mediaList = new MediaList();
+  ULONG code=0;
+  code=extractULONG();
+  ULONG numEntries=extractULONG();
+  Log::getInstance()->log("VDR", Log::DEBUG, "receiveMediaList with %d entries",numEntries);
+  while (packetPos < packetLength && numEntries >0)
+  {
+    Media* m = new Media();
+    ULONG mtype = extractULONG();
+    ULONG mtime=extractULONG();
+    ULONG flags=0;
+    flags=extractULONG();
+    ULONG stsize=extractULONG();
+    char * name=extractString();
+    if (! name || stsize != (strlen(name)+1)) {
+      Log::getInstance()->log("VDR", Log::ERR, "receiveMediaList invalid packet entry, read size %d, strlen %d", stsize, strlen(name)+1);
+      delete m;
+      delete mediaList;
+      freePacket();
+      MUTEX_UNLOCK(&mutex);
+      return NULL;
+      }
+    //ignore . and .. entries
+    if (strcmp(name,".") == 0 || strcmp(name,"..")==0) {
+  delete m;
+  continue;
+    }
+    m->setFileName(name);
+    m->setTime(mtime);
+    m->setMediaType(mtype);
+    mediaList->push_back(m);
+    Log::getInstance()->log("VDR", Log::DEBUG, "Have added a media to list. %s, type=%d, time=%d", name,mtype,mtime);
+    numEntries--;
+  }
+
+  freePacket();
+  MUTEX_UNLOCK(&mutex);
+
+  return mediaList;
+}
+/**
+  * get image Request:
+  * 4 length
+  * 4 VDR_GETIMAGE
+  * 4 flags (currently unused)
+  * 4 x size
+  * 4 y size
+  * n filename
+  * n+1 0
+  * get image response:
+  * 4 length
+  * 4 VDR_GETIMAGE
+  * 4 len of image
+*/
+ULONG VDR::loadImage(const char* fileName, ULONG x, ULONG y)
+{
+  unsigned long totalLength = 20 + strlen(fileName) + 1;
+  UCHAR* buffer = new UCHAR[totalLength];
+
+  *(unsigned long*)&buffer[0] = htonl(totalLength - 4);
+  *(unsigned long*)&buffer[4] = htonl(VDR_GETIMAGE);
+  *(unsigned long*)&buffer[8] = htonl(0);
+  *(unsigned long*)&buffer[8] = htonl(x);
+  *(unsigned long*)&buffer[8] = htonl(y);
+  strcpy((char*)&buffer[12], fileName);
+
+  MUTEX_LOCK(&mutex);
+  if (!connected) { MUTEX_UNLOCK(&mutex); return 0; }
+
+  unsigned int a = tcp->sendPacket(buffer, totalLength);
+  delete []buffer;
+
+  if (a != totalLength)
+  {
+    disconnect();
+    MUTEX_UNLOCK(&mutex);
+    return 0;
+  }
+
+  if (!getPacket())
+  {
+    MUTEX_UNLOCK(&mutex);
+    return 0;
+  }
+  ULONG cmd=0;
+  cmd=extractULONG();
+  ULONG lengthBytes = extractULONG();
+  freePacket();
+  MUTEX_UNLOCK(&mutex);
+
+  Log::getInstance()->log("VDR", Log::DEBUG, "getImage %s: %lu", fileName,lengthBytes);
+
+  return lengthBytes;
+}
diff --git a/vdr.h b/vdr.h
index 2cb41e72a9ac63a3f017d80d2206de4f93d4530f..ce74fea94b80738cf375231b088fc5f30fa5e185 100644 (file)
--- a/vdr.h
+++ b/vdr.h
 #include "recinfo.h"
 #include "mark.h"
 #include "wol.h"
+#include "media.h"
 
 using namespace std;
 
 typedef vector<Event*> EventList;
 typedef vector<Channel*> ChannelList;
 typedef vector<RecTimer*> RecTimerList;
-typedef vector<Mark*> MarkList;
 
 struct VDRServer
 {
@@ -131,6 +131,8 @@ class VDR
     void         getChannelPids(Channel* channel);
 
     UCHAR*     getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived);
+    //get image blocks separate - we can do this in parallel
+    UCHAR*     getImageBlock(ULONG position, UINT maxAmount, UINT* amountReceived);
     int        stopStreaming();
     EventList* getChannelSchedule(ULONG number);
     EventList* getChannelSchedule(ULONG number, time_t start, ULONG duration);
@@ -139,6 +141,16 @@ class VDR
     ULONG      setEventTimer(char* timerString);
 
     RecTimerList* getRecTimersList();
+    /**
+      * ge a list of media entries
+      * if parent==NULL this is the configured base list
+      */
+    MediaList* getMediaList(const char* parent=NULL,int mediaType=MEDIA_TYPE_ALL);
+    /**
+      * start loading a JPEG image
+      * return size, 0 if not found
+      */
+    ULONG loadImage(const char * filename, ULONG xsize=0,ULONG ysize=0);
 
     // end
 
@@ -154,6 +166,7 @@ class VDR
 
 
   private:
+    UCHAR*     getBlock(ULLONG position, UINT maxAmount, UINT* amountReceived, ULONG cmd);
     static VDR* instance;
     Log* logger;
     int initted;
@@ -197,7 +210,9 @@ class VDR
     const static ULONG VDR_GETRECINFO          = 20;
     const static ULONG VDR_GETMARKS            = 21;
     const static ULONG VDR_GETCHANNELPIDS      = 22;
-
+    const static ULONG VDR_GETMEDIALIST        = 30;
+    const static ULONG VDR_GETIMAGE            = 31;
+    const static ULONG VDR_GETIMAGEBLOCK       = 32;
     int  getPacket();
     void freePacket();
     int  serverError();
diff --git a/vmedialist.cc b/vmedialist.cc
new file mode 100644 (file)
index 0000000..baaa129
--- /dev/null
@@ -0,0 +1,710 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include <vector>
+#include "unistd.h"
+#include "vmedialist.h"
+#include <time.h>
+#include <string.h>
+#include "vpicture.h"
+#include "vaudioplayer.h"
+
+
+using namespace std;
+class MediaDirectory {
+       private:
+               char * dirname;
+               char * displayname;
+               char * fullpath;
+               int selection;
+               int sortorder;
+               ULONG mtype;
+       public:
+               void setDirname(const char * n) {
+                       if (dirname) delete dirname;
+                       dirname=NULL;
+                       if (!n) return;
+                       dirname=new char[strlen(n)+1];
+                       strcpy(dirname,n);
+               }
+               void setDisplayname(const char * n) {
+                       if (displayname) delete displayname;
+                       displayname=NULL;
+                       if (!n) return;
+                       displayname=new char[strlen(n)+1];
+                       strcpy(displayname,n);
+               }
+               void setFullpath(const char * n) {
+                       if (fullpath) delete fullpath;
+                       fullpath=NULL;
+                       if (!n) return;
+                       fullpath=new char[strlen(n)+1];
+                       strcpy(fullpath,n);
+               }
+               void setSelection(int s) {
+                       selection=s;
+               }
+               void setMediaType(ULONG n) {mtype=n;}
+               void setSortorder(int s) {
+                       sortorder=s;
+               }
+               const char * getDirname() {
+                       return dirname;
+               }
+               const char * getDisplayname() {
+                       if (displayname) return displayname;
+                       if (fullpath) return fullpath;
+                       if (dirname) return dirname;
+                       return "/";
+               }
+               const char * getFullPath() {
+                       return fullpath;
+               }
+               int getSelection() {
+                       return selection;
+               }
+               ULONG getMediaType(){return mtype;}
+               int getSortorder() {
+                       return sortorder;
+               }
+       public:
+               MediaDirectory(const char *d) : dirname(NULL),displayname(NULL),fullpath(NULL),selection(0),
+           sortorder(0),mtype(MEDIA_TYPE_ALL){
+                 setDirname(d);
+                 }
+               MediaDirectory(MediaDirectory &c) {
+                       MediaDirectory(c.getDirname());
+                       setDisplayname(c.getDisplayname());
+                       setSelection(c.getSelection());
+                       setMediaType(c.getMediaType());
+                       setSortorder(c.getSortorder());
+               }
+               ~MediaDirectory() {
+                       if (dirname) delete dirname;
+                       if (displayname) delete displayname;
+                       if (fullpath) delete fullpath;
+               }
+};
+
+typedef vector<MediaDirectory*> MDirList;
+class DirList {
+       private:
+               int current;
+    MDirList list;
+       public:
+               DirList() {
+                       current=0;
+                       list.push_back(new MediaDirectory(NULL));
+               }
+               ~DirList() {
+                       MDirList::iterator it;
+                       for (it=list.begin();it<list.end();it++) {
+                               delete *it;
+                       }
+               }
+               MediaDirectory *getCurrent() {
+                       return list[current];
+               }
+               const char * getPath() {
+                       return getCurrent()->getFullPath();
+               }
+               int dropTop() {
+                       if (current > 0) {
+                               current--;
+                               delete list.back();
+                               list.pop_back();
+                       }
+                       return current;
+               }
+               int append(const char *dirname) {
+                       if (! dirname) return current;
+                       MediaDirectory* md=new MediaDirectory(dirname);
+                       const char *cp=getCurrent()->getFullPath();
+                       int len=strlen(dirname)+2;
+                       if (cp) len+=strlen(cp);
+                       char * fp=new char[len];
+                       *fp=0;
+                       if (cp) {
+                               strcpy(fp,cp);
+                               strcat(fp,"/");
+                       }
+                       else if (*dirname != '/' ) strcpy(fp,"/");
+                       strcat(fp,dirname);
+                       md->setFullpath(fp);
+                       delete fp;
+                       list.push_back(md);
+                       current++;
+                       return current;
+               }
+
+               int getLevel() {
+                       return current;
+               }
+
+};
+
+
+class MediaSorterName
+{
+  public:
+  bool operator() (const Media* a, const Media* b)
+  {
+    if (b->getMediaType() == MEDIA_TYPE_DIR &&
+         a->getMediaType() != MEDIA_TYPE_DIR)
+      return false;
+    if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+        a->getMediaType() == MEDIA_TYPE_DIR)
+      return true;
+    int c = strcmp(b->getFileName(), a->getFileName());
+    if (c > 0) return true;
+    return false;
+  }
+};
+
+//a sorter with a randomly initialized order
+class MediaSorterRandom
+{
+  public:
+  MediaSorterRandom(int start) {
+    mystart=start;
+  }
+  bool operator() (const Media* a, const Media* b)
+  {
+    if (b->getMediaType() == MEDIA_TYPE_DIR &&
+         a->getMediaType() != MEDIA_TYPE_DIR)
+      return false;
+    if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+        a->getMediaType() == MEDIA_TYPE_DIR)
+      return true;
+    unsigned char suma=sum(a->getFileName(),(unsigned char)mystart);
+    unsigned char sumb=sum(b->getFileName(),(unsigned char)mystart);
+    if (sumb > suma) return true;
+    return false;
+  }
+  private:
+  unsigned char sum(const char *name,unsigned char start) {
+    for (;*name!=0;name++) start=start ^ (unsigned char)*name;
+    return start;
+  }
+  int mystart;
+};
+
+
+struct MediaSorterTime
+{
+  bool operator() (const Media* a, const Media* b)
+  {
+    if (b->getMediaType() == MEDIA_TYPE_DIR &&
+         a->getMediaType() != MEDIA_TYPE_DIR)
+      return false;
+    if ( b->getMediaType() != MEDIA_TYPE_DIR &&
+        a->getMediaType() == MEDIA_TYPE_DIR)
+      return true;
+    if (b->getTime()>a->getTime()) return true;
+    return false;
+  }
+};
+
+
+VMediaList::VMediaList()
+{
+       dirlist=new DirList();
+  Log::getInstance()->log("VMediaList::VMediaList", Log::DEBUG, "dirlist=%p,curren=%p",dirlist,dirlist->getCurrent());
+       dirlist->getCurrent()->setSortorder(SORT_NAME);
+  create(570, 420);
+  if (Video::getInstance()->getFormat() == Video::PAL)
+  {
+    setScreenPos(80, 70);
+  }
+  else
+  {
+    setScreenPos(70, 35);
+  }
+
+  setBackgroundColour(Colour::VIEWBACKGROUND);
+  setTitleBarOn(1);
+  setTitleBarColour(Colour::TITLEBARBACKGROUND);
+
+  sl.setSurface(surface);
+  sl.setSurfaceOffset(10, 30 + 5);
+  sl.setDimensions(area.w - 20, area.h - 30 - 15 - 30);
+  sl.addColumn(0);
+  sl.addColumn(60);
+  mediaList=NULL;
+  loading=true;
+  sortOrder=SORT_NONE;
+
+
+}
+
+VMediaList::~VMediaList()
+{
+       delete dirlist;
+  clearMediaList();
+}
+
+void VMediaList::clearMediaList() {
+  if (mediaList)
+  {
+    for (UINT i = 0; i < mediaList->size(); i++)
+    {
+      delete (*mediaList)[i];
+    }
+
+    mediaList->clear();
+    delete mediaList;
+  }
+}
+
+
+
+int VMediaList::getNumEntries(int mediaType,int lowerThen) {
+  if (mediaType == MEDIA_TYPE_ALL && lowerThen < 0) return mediaList->size();
+  if (lowerThen < 0) lowerThen=mediaList->size();
+  int rt=0;
+  for (int i=0;i<(int)(mediaList->size()) && i <= lowerThen;i++) {
+    if ((*mediaList)[i]->getMediaType() & mediaType) rt++;
+  }
+  return rt;
+}
+
+void VMediaList::setList(MediaList* tlist)
+{
+  if (mediaList != tlist) {
+    clearMediaList();
+  }
+  sortOrder=SORT_NONE;
+  mediaList = tlist;
+  sortList(dirlist->getCurrent()->getSortorder());
+  updateSelectList(dirlist->getCurrent()->getSelection());
+}
+
+
+void VMediaList::updateSelectList(){
+       updateSelectList(-1);
+}
+void VMediaList::updateSelectList(int currentNumber){
+  char str[5000];
+  char tempA[Media::TIMEBUFLEN];
+  Log::getInstance()->log("VMediaList::updateSelectList", Log::DEBUG, "media=%p",mediaList);
+
+  ULONG currentSelection=0;
+  if (sl.getNumOptions() >= 1 && currentNumber < 0) {
+    currentSelection=sl.getCurrentOptionData();
+       }
+  sl.clear();
+
+  Media* media;
+  int first = 1;
+  if (mediaList)
+  {
+    for (UINT i = 0; i < mediaList->size(); i++)
+    {
+      media = (*mediaList)[i];
+      if (media->getMediaType() == MEDIA_TYPE_DIR) {
+        sprintf(str, "%04u  %s  [%s]", i,media->getTimeString(tempA), media->getDisplayName());
+        //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str);
+        media->index = sl.addOption(str, (ULONG)media, first);
+      }
+      else {
+        sprintf(str, "%04u  %s  %s", i,media->getTimeString(tempA), media->getDisplayName());
+        //Log::getInstance()->log("VMediaList", Log::DEBUG, "add to select list %s",str);
+        media->index = sl.addOption(str, (ULONG)media, first);
+      }
+      first = 0;
+    }
+  }
+       if (currentNumber >= 0) sl.hintSetCurrent(currentNumber);
+       else sl.hintSetCurrent(0);
+  if (currentSelection != 0) {
+    bool found=false;
+    //position to the previous selection
+    for (int i=0;i<sl.getNumOptions();i++) {
+      sl.hintSetCurrent(i);
+      if (sl.getCurrentOptionData() == currentSelection) {
+        found=true;
+        break;
+      }
+    }
+    if (! found) sl.hintSetCurrent(0);
+  }
+  loading=false;
+  if (sl.getNumOptions() > 0)
+    sl.draw();
+  doShowingBar();
+}
+
+void VMediaList::highlightMedia(Media* media)
+{
+  sl.hintSetCurrent(media->index);
+  sl.draw();
+  doShowingBar();
+  ViewMan::getInstance()->updateView(this);
+}
+
+void VMediaList::draw()
+{
+  Log::getInstance()->log("VMediaList::draw", Log::DEBUG, "namestr=%s",dirlist->getCurrent()->getDisplayname());
+  char title[400];
+  SNPRINTF(title, 398, tr("Media - %s"), dirlist->getCurrent()->getDisplayname());
+  title[399]=0;
+  setTitleText(title);
+  View::draw();
+  if (loading)
+  {
+    drawText(tr("Loading..."), 240, 180, Colour::LIGHTTEXT);
+  }
+  else {
+  if (sl.getNumOptions() > 0) sl.draw();
+
+  // Put the status stuff at the bottom
+
+  WSymbol w;
+  w.setSurface(surface);
+
+  w.nextSymbol = WSymbol::UP;
+  w.setSurfaceOffset(20, 385);
+  w.draw();
+
+  w.nextSymbol = WSymbol::DOWN;
+  w.setSurfaceOffset(50, 385);
+  w.draw();
+
+  w.nextSymbol = WSymbol::SKIPBACK;
+  w.setSurfaceOffset(85, 385);
+  w.draw();
+
+  w.nextSymbol = WSymbol::SKIPFORWARD;
+  w.setSurfaceOffset(115, 385);
+  w.draw();
+
+  w.nextSymbol = WSymbol::PLAY;
+  w.setSurfaceOffset(150, 385);
+  w.draw();
+
+  doShowingBar();
+  }
+}
+
+void VMediaList::doShowingBar()
+{
+  int topOption = sl.getTopOption() + 1;
+  if (sl.getNumOptions() == 0) topOption = 0;
+
+  char showing[250];
+  char * strmode=tr("Name");
+  switch (sortOrder) {
+    case SORT_TIME:
+      strmode=tr("Rand");
+      break;
+    case SORT_NAME:
+      strmode=tr("Time");
+      break;
+    default:
+      break;
+  }
+  snprintf(showing, 250,tr("%i to %i of %i"), 
+      topOption, sl.getBottomOption(), sl.getNumOptions());
+
+//  Box b;
+//  b.setSurfaceOffset(220, 385);
+//  b.setDimensions(160, 25);
+//  b.fillColour(Colour::VIEWBACKGROUND);
+//  b.drawText(showing, 0, 0, Colour::LIGHTTEXT);
+
+  rectangle(200, 384, 18, 16, Colour::VIDEOBLUE);
+  rectangle(220, 385, 220+160, 385+25, Colour::VIEWBACKGROUND);
+  drawText(strmode, 220, 385, Colour::LIGHTTEXT);
+  drawText(showing, 280, 385, Colour::LIGHTTEXT);
+  if (sl.getCurrentOptionData() != 0) Log::getInstance()->log("VMediaList",Log::DEBUG,"selected %s",((Media *)sl.getCurrentOptionData())->getDisplayName());
+}
+
+Media * VMediaList::getMedia(int ltype,ULONG move) {
+  int cur=sl.getCurrentOption();
+  Media *m;
+  bool more=true;
+  while (more) {
+    int last=sl.getCurrentOption();
+    switch (move) {
+      case MV_NEXT:
+        sl.down();
+        break;
+      case MV_PREV:
+        sl.up();
+        break;
+      default:
+        more=false;
+      break;
+    }
+    m=(Media*)sl.getCurrentOptionData();
+    if (m->getMediaType() & ltype) {
+                       sl.draw();
+                       return m;
+               }
+    //stop if we are done
+    if (sl.getCurrentOption() == cur || sl.getCurrentOption() == last) break;
+  }
+  return NULL;
+}
+
+int VMediaList::handleCommand(int command)
+{
+  switch(command)
+  {
+               case Remote::ONE:
+                       {
+      sl.hintSetCurrent(0);
+      sl.draw();
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+      return 2;
+                       }
+    case Remote::DF_UP:
+    case Remote::UP:
+    {
+      sl.up();
+      sl.draw();
+
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+      return 2;
+    }
+    case Remote::DF_DOWN:
+    case Remote::DOWN:
+    {
+      sl.down();
+      sl.draw();
+
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+      return 2;
+    }
+    case Remote::SKIPBACK:
+    {
+      sl.pageUp();
+      sl.draw();
+
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+      return 2;
+    }
+    case Remote::SKIPFORWARD:
+    {
+      sl.pageDown();
+      sl.draw();
+
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+      return 2;
+    }
+    case Remote::BLUE:
+    {
+      switch(sortOrder) {
+        case SORT_NAME:
+          sortList(SORT_TIME);
+          ViewMan::getInstance()->updateView(this);
+          return 2;
+        case SORT_TIME:
+          sortList(SORT_RANDOM);
+          ViewMan::getInstance()->updateView(this);
+          return 2;
+        default:
+          sortList(SORT_NAME);
+          ViewMan::getInstance()->updateView(this);
+          return 2;
+      }
+    }
+    case Remote::OK:
+    case Remote::PLAY:
+    {
+      Media* media = NULL;
+      if (mediaList) media = (Media*)sl.getCurrentOptionData();
+      if (media == NULL) return 2;
+      Log::getInstance()->log("VMediaList", Log::DEBUG, "activated %lu", media->index);
+      switch(media->getMediaType())
+      {
+        case MEDIA_TYPE_DIR:
+        { 
+        //create child
+        Log::getInstance()->log("VMediaList", Log::DEBUG, "create child for %s",media->getFileName());
+        if (media->getFileName() == NULL ) return 2;
+                               if (sl.getNumOptions() >=1) {
+                                       dirlist->getCurrent()->setSelection(sl.getCurrentOption());
+                               }
+                               dirlist->getCurrent()->setSortorder(sortOrder);
+                               dirlist->append(media->getFileName());
+                               //same sort order for next level
+                               dirlist->getCurrent()->setSortorder(sortOrder);
+                               load();
+        break;
+        }
+        case MEDIA_TYPE_AUDIO:
+        Log::getInstance()->log("VMediaList", Log::DEBUG, "play audio file %s",
+          media->getFileName());
+        VAudioplayer::createPlayer(this,command==Remote::PLAY);
+        break;
+        case MEDIA_TYPE_VIDEO:
+        Log::getInstance()->log("VMediaList", Log::DEBUG, "play video file %s",
+          media->getFileName());
+        //play video
+        break;
+        case MEDIA_TYPE_PICTURE:
+        Log::getInstance()->log("VMediaList", Log::DEBUG, "show picture file %s",
+         media->getFileName());
+        VPicture::createViewer(this,command==Remote::PLAY);
+              //play video
+        break;
+        default:
+        Log::getInstance()->log("VMediaList", Log::DEBUG, "unknown media type %d file %s",
+          media->getMediaType(),media->getFileName());
+
+        }
+      /*
+      VVideoLive* v = new VVideoLive(mediaList, media->type, this);
+
+      v->draw();
+      ViewMan::getInstance()->add(v);
+      ViewMan::getInstance()->updateView(v);
+
+      v->medianelChange(VVideoLive::NUMBER, media->number);
+      */
+
+      return 2;
+    }
+    case Remote::BACK:
+    {
+                       if (dirlist->getLevel() < 1) return 4;
+                       dirlist->dropTop();
+                       load();
+    }
+  }
+  // stop command getting to any more views
+  return 1;
+}
+
+void VMediaList::processMessage(Message* m)
+{
+  if (m->message == Message::MOUSE_MOVE)
+  {
+    if (sl.mouseMove((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
+    {
+      sl.draw();
+      doShowingBar();
+      ViewMan::getInstance()->updateView(this);
+    }
+  }
+  else if (m->message == Message::MOUSE_LBDOWN)
+  {
+    if (sl.mouseLBDOWN((m->parameter>>16)-getScreenX(),(m->parameter&0xFFFF)-getScreenY()))
+    {
+      ViewMan::getInstance()->handleCommand(Remote::OK); //simulate OK press
+    }
+    else
+    { //check if press is outside this view! then simulate cancel
+      int x=(m->parameter>>16)-getScreenX();
+      int y=(m->parameter&0xFFFF)-getScreenY();
+      if (x<0 || y <0 || x>getWidth() || y>getHeight())
+      {
+        ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+      }
+    }
+  }
+}
+
+int VMediaList::createList() {
+   Log::getInstance()->log("VMediaList::createList", Log::DEBUG, "");
+   VMediaList *vmn=new VMediaList();
+   //show the "loading" indicator
+   ViewMan::getInstance()->add(vmn);
+   int rt=vmn->load();
+   if ( rt != 0) {
+     ViewMan::getInstance()->removeView(vmn);
+     }
+   return rt;
+}
+
+void VMediaList::sortList(int newSort) {
+  if (sortOrder == newSort) return;
+   Log::getInstance()->log("VMediaList::sortList", Log::DEBUG, "p=%p,sort=%d, size=%d",this,newSort,mediaList->size());
+   if (mediaList->begin() != mediaList->end()) {
+   switch (newSort) {
+     case SORT_TIME:
+       ::sort(mediaList->begin(),mediaList->end(),MediaSorterTime());
+       break;
+     case SORT_NAME:
+       ::sort(mediaList->begin(),mediaList->end(),MediaSorterName());
+       break;
+     case SORT_RANDOM:
+       ::sort(mediaList->begin(),mediaList->end(),MediaSorterRandom(time(NULL)));
+       break;
+     }
+   }
+   sortOrder=newSort;
+   updateSelectList();
+}
+
+
+int VMediaList::load() {
+       
+       loading=true;
+       draw();
+  ViewMan::getInstance()->updateView(this);
+  VDR* vdr=VDR::getInstance();
+   Log::getInstance()->log("VMediaList::load", Log::DEBUG, "load list for %s",dirlist->getPath());
+  if (vdr->isConnected()) {
+                MediaDirectory *md=dirlist->getCurrent();
+     MediaList *mn=vdr->getMediaList(md->getFullPath(),md->getMediaType());
+     if (mn != NULL) {
+       setList(mn);
+                        draw();
+       ViewMan::getInstance()->updateView(this);
+       return 0;
+       }
+  }
+  if (! vdr->isConnected()) {
+    Command::getInstance()->connectionLost();
+  }
+  else {
+    Log::getInstance()->log("VMediaList", Log::ERR, "unable to get MediaList for %s",dirlist->getPath());
+    VInfo* vi = new VInfo();
+    vi->create(400, 150);
+    vi->setExitable();
+    vi->setBorderOn(1);
+    vi->setTitleBarOn(0);
+
+    if (Video::getInstance()->getFormat() == Video::PAL)
+      vi->setScreenPos(170, 200);
+    else
+      vi->setScreenPos(160, 150);
+    vi->setOneLiner(tr("unable to get media list"));
+    vi->draw();
+
+    Message* m = new Message();
+    m->message = Message::ADD_VIEW;
+    m->to = ViewMan::getInstance();
+    m->parameter = (ULONG)vi;
+    Command::getInstance()->postMessageNoLock(m);
+  }
+  return 1;
+}
+
+const char * VMediaList::getDirname() const {
+       return dirlist->getCurrent()->getFullPath();
+}
diff --git a/vmedialist.h b/vmedialist.h
new file mode 100644 (file)
index 0000000..ff2b541
--- /dev/null
@@ -0,0 +1,96 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef VMEDIALIST_H
+#define VMEDIALIST_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "wselectlist.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "vvideolive.h"
+#include "colour.h"
+#include "video.h"
+#include "i18n.h"
+
+class DirList;
+class VMediaList : public View
+{
+  public:
+    VMediaList();
+    ~VMediaList();
+
+    void setList(MediaList* chanList);
+    void highlightMedia(Media* media);
+    const char *getDirname() const;
+    void processMessage(Message* m);
+    int handleCommand(int command);
+    void draw();
+
+    //factory method
+    //return 0 on success
+    static int createList();
+
+    //move functions for getMedia
+    const static ULONG MV_NEXT=1;
+    const static ULONG MV_PREV=2;
+    const static ULONG MV_RND=3;
+    const static ULONG MV_NONE=0;
+
+    //move selection to the next matching media
+    //with the given move
+    //return NULL if none found
+    Media * getMedia(int type,ULONG move=MV_NONE);
+
+               //get the number of media entries of particular types in this list
+               //if lowerThen is set, only count entries lt this one
+               int getNumEntries(int mediaType,int lowerThen=-1);
+
+  private:
+    /**
+      * fill the medialist basing on the current dirname
+      */
+    int load();
+    MediaList    *mediaList;
+    WSelectList sl;
+    bool         loading;
+    void doShowingBar();
+               int          sortOrder;
+               //sort list defined by new order
+               void         sortList(int order);
+               static const int SORT_NONE=0;
+               static const int SORT_NAME=1;
+               static const int SORT_TIME=2;
+               static const int SORT_RANDOM=3;
+               void         clearMediaList();
+               void         updateSelectList();
+               void         updateSelectList(int current);
+               DirList*    dirlist;
+
+};
+
+#endif
diff --git a/vpicture.cc b/vpicture.cc
new file mode 100644 (file)
index 0000000..139d587
--- /dev/null
@@ -0,0 +1,420 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "vpicture.h"
+#include "vpicturebanner.h"
+#include <time.h>
+
+Colour VPicture::pictureBack=Colour(140,140,140);
+Colour VPicture::infoBack=Colour(110,110,110);
+//the jpeg reader
+
+class VPreader : public JpegReader {
+  private:
+    VPicture * parent;
+  public:
+  VPreader(VPicture *p){
+     parent=p;};
+  virtual ULONG readChunk(ULONG offset,ULONG len,char ** buf) {
+     Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "read chunk o=%d,len=%d,buf=%p",
+       offset,len,*buf);
+     UINT numrec=0;
+     *buf=(char *)VDR::getInstance()->getImageBlock(offset,(UINT)len,&numrec);
+     Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "got n=%d,buf=%p",
+       numrec,*buf);
+     return numrec;
+     }
+  virtual ULONG initRead(const char *filename) {
+     Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s",filename);
+     Video* video = Video::getInstance();
+     ULONG size=VDR::getInstance()->loadImage(filename,video->getScreenWidth(), video->getScreenHeight());
+     Log::getInstance()->log("VPicture::jpegReader", Log::DEBUG, "load image %s returned %d",filename,size);
+     return size;
+     }
+};
+
+VPicture::VPicture(VMediaList *p)
+{
+  parent=p;
+  reader=new VPreader(this);
+  needDraw=false;
+  Video* video = Video::getInstance();
+  create(video->getScreenWidth(), video->getScreenHeight());
+  jpeg.setSurface(surface);
+       jpeg.setDimensions(area.w,area.h);
+  banner=NULL;
+  fullname=NULL;
+  filename=NULL;
+  ftime=0;
+  slideshow=false;
+  showtime=INITIAL_SHOWTIME;
+  mediaError=NULL;
+  currentMedia=NULL;
+  shortBanner=false;
+  rotate=0;
+  info=NULL;
+  jpeg.setBackgroundColour(pictureBack);
+}
+
+VPicture::~VPicture()
+{
+  delete reader;
+  if (banner) ViewMan::getInstance()->removeView(banner);
+  if (fullname) delete fullname;
+  if (filename) delete filename;
+  Timers::getInstance()->cancelTimer(this,1);
+  Timers::getInstance()->cancelTimer(this,2);
+  Timers::getInstance()->cancelTimer(this,3);
+  destroyInfo();
+  
+}
+
+void VPicture::draw()
+{
+  Log::getInstance()->log("VPicture::draw", Log::DEBUG, "needDraw=%d,p=%p",
+       needDraw,this);
+  View::draw();
+  if (mediaError) {
+    drawText(mediaError,20,area.h-10,Colour::LIGHTTEXT);
+    return;
+    }
+  if (needDraw) {
+    jpeg.draw();
+    }
+}
+
+
+int VPicture::handleCommand(int command)
+{
+  Timers::getInstance()->cancelTimer(this,1);
+  int rt=1;
+  switch(command)
+  {
+    case Remote::DF_UP:
+    case Remote::UP:
+    case Remote::SKIPBACK:
+      showPicture(VMediaList::MV_PREV);
+      ViewMan::getInstance()->updateView(this);
+      rt= 2;
+      break;
+    case Remote::FORWARD:
+      if (showtime > 1) showtime--;
+      updateBanner(true);
+      break;
+    case Remote::DF_DOWN:
+    case Remote::DOWN:
+    case Remote::SKIPFORWARD:
+      showPicture(VMediaList::MV_NEXT);
+      ViewMan::getInstance()->updateView(this);
+      rt= 2;
+      break;
+    case Remote::REVERSE:
+      if (showtime < 50 ) showtime++;
+      updateBanner(true);
+      break;
+    case Remote::OK:
+    {
+      if (banner) {
+        destroyBanner();
+        destroyInfo();
+        }
+      else showBanner();
+      rt= 2;
+    }
+    break;
+    case Remote::PLAY:
+    {
+      slideshow=true;
+      showPicture(VMediaList::MV_NEXT);
+      ViewMan::getInstance()->updateView(this);
+      rt= 2;
+    }
+    break;
+    case Remote::PAUSE:
+      slideshow=false;
+      updateBanner();
+      rt= 2;
+      break;
+    case Remote::STOP:
+      slideshow=false;
+      showtime=INITIAL_SHOWTIME;
+      updateBanner();
+      rt= 2;
+      break;
+    case Remote::RED:
+      switch(rotate) {
+        case WJpeg::ROT_0:
+          rotate=WJpeg::ROT_90;
+          break;
+        case WJpeg::ROT_90:
+          rotate=WJpeg::ROT_180;
+          break;
+        case WJpeg::ROT_180:
+          rotate=WJpeg::ROT_270;
+          break;
+        case WJpeg::ROT_270:
+          rotate=WJpeg::ROT_0;
+          break;
+        }
+      showPicture(VMediaList::MV_NONE,rotate);
+      ViewMan::getInstance()->updateView(this);
+      rt=2;
+      break;
+    case Remote::GREEN:
+      if (info) destroyInfo();
+      else showInfo();
+      rt=2;
+      break;
+    case Remote::BACK:
+    {
+      rt= 4;
+    }
+    break;
+  }
+  if (slideshow) startSlideshow();
+  // stop command getting to any more views
+  return rt;
+}
+
+void VPicture::processMessage(Message* m)
+{
+  if (m->message == Message::MOUSE_MOVE)
+  {
+    ;
+  }
+  else if (m->message == Message::MOUSE_LBDOWN)
+  {
+    
+    //check if press is outside this view! then simulate cancel
+    int x=(m->parameter>>16)-getScreenX();
+    int y=(m->parameter&0xFFFF)-getScreenY();
+    if (x<0 || y <0 || x>getWidth() || y>getHeight())
+    {
+      ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+    }
+  }
+}
+
+VPicture * VPicture::createViewer(VMediaList * mparent, bool bslideshow) {
+   Log::getInstance()->log("VPicture::createViewer", Log::DEBUG, "p=%p",
+       mparent);
+   VPicture *vmn=new VPicture(mparent);
+   ViewMan::getInstance()->add(vmn);
+   ViewMan::getInstance()->updateView(vmn);
+   vmn->showPicture();
+   if (bslideshow) vmn->startSlideshow();
+   vmn->showBanner();
+   ViewMan::getInstance()->updateView(vmn);
+   return vmn;
+}
+
+void VPicture::startSlideshow() {
+   slideshow=true;
+   Timers::getInstance()->setTimerD(this,1,showtime);
+}
+
+void VPicture::showPicture(ULONG move, int rt) {
+  rotate=rt;
+  currentMedia=parent->getMedia(MEDIA_TYPE_PICTURE,move);
+  load(currentMedia);
+  if (mediaError || jpeg.hasError()) destroyInfo();
+  else updateInfo();
+  ViewMan::getInstance()->updateView(this);
+}
+
+
+int VPicture::load(Media *md) {
+  jpeg.init(NULL,false,NULL);
+  mediaError=tr("No media found");
+  needDraw=false;
+  if (! md) return 1;
+  const char *fname=md->getFileName();
+  ftime=md->getTime();
+  int len=strlen(fname)+2+strlen(parent->getDirname());
+  if (fullname) {
+     delete fullname;
+     fullname=NULL;
+     }
+  fullname=new char[len];
+  sprintf(fullname,"%s/%s",parent->getDirname(),fname);
+  if (filename) delete filename;
+  len=strlen(fname)+1;
+  filename=new char[len];
+  strcpy(filename,fname);
+  Log::getInstance()->log("VPicture::load", Log::DEBUG, "filename=%s,p=%p",
+       fullname,this);
+  VDR* vdr=VDR::getInstance();
+  //do we have a banner?
+  bool haveBanner=banner!=NULL && ! shortBanner;
+  if (vdr->isConnected()) {
+     showBanner(true);
+     jpeg.init(fullname,false,reader);
+     jpeg.setRotate(rotate);
+     needDraw=true;
+     mediaError=NULL;
+     draw();
+     //only show the banner if it was there before
+     if (haveBanner) showBanner();
+     else {
+        if(banner) destroyBanner();
+        }
+     Log::getInstance()->log("VPicture::load", Log::DEBUG, "success: filename=%s,p=%p",
+       fullname,this);
+     return 0;
+  }
+  else {
+    mediaError=tr("no VDR connection");
+    }
+  return 1;
+}
+
+void VPicture::showBanner(bool loading,int shortDisplay) {
+  //if the loading flag is set,
+       //we are in the main thread - so we can (and must) safely hard destroy/create the banner
+  Timers::getInstance()->cancelTimer(this,2);
+  if (! filename || ! currentMedia) {
+    //hmm...
+    destroyBanner(!loading);
+    return;
+    }
+  if (banner) destroyBanner(!loading);
+  banner= new VPictureBanner(this, loading, slideshow);
+  banner->setBackgroundColour(infoBack);
+  if (! loading) {
+    int len=strlen(filename)+Media::TIMEBUFLEN+20;
+    char buf[len];
+    char tbuf[Media::TIMEBUFLEN];
+    snprintf(buf,len,"%c%02ds%c %s %s ",
+      slideshow?' ':'[',
+      showtime,
+      slideshow?' ':']',
+      currentMedia->getTimeString(tbuf),
+      filename);
+    banner->setText(buf);
+  }
+  else {
+    banner->setText(filename);
+  }
+  banner->draw();
+  if (shortDisplay != 0) shortBanner=true;
+  if (! slideshow && shortDisplay == 0) shortDisplay=8;
+  //OK we start timer if we don't load and either shortDisplay or no slideshow
+  if (! loading && shortDisplay != 0)   Timers::getInstance()->setTimerD(this,2,shortDisplay);
+  if (! loading) sendViewMsg(banner,false);
+       else {
+               ViewMan::getInstance()->add(banner);
+               ViewMan::getInstance()->updateView(banner);
+       }
+
+  }
+
+void VPicture::destroyBanner(bool fromTimer) {
+  shortBanner=false;
+  if (banner) {
+    if (fromTimer) sendViewMsg(banner,true);
+               else ViewMan::getInstance()->removeView(banner);
+    banner=NULL;
+    if (! fromTimer) Timers::getInstance()->cancelTimer(this,2);
+    }
+}
+void VPicture::updateBanner(bool shortDisplay) {
+  if (banner && ! shortBanner) {
+    showBanner(false);
+    }
+  else if (shortDisplay) {
+    showBanner(false,2);
+    }
+}
+void VPicture::timercall(int clientref) {
+   switch(clientref)
+   {
+      case 1:
+        if (! slideshow) return;
+        Log::getInstance()->log("VPicture::timercall", Log::DEBUG, "slideshow");
+                               sendCommandMsg(Remote::PLAY);
+        break;
+      case 2:
+        destroyBanner(true);
+        break;
+      case 3:
+        destroyInfo(true);
+        break;
+    }
+   
+  }
+
+#define INFOBUF 1000
+void VPicture::showInfo(){
+  if (info) destroyInfo();
+  if (! currentMedia) return;
+  info=new VInfo();
+  info->setTitleText(currentMedia->getFileName());
+  info->setDropThrough();
+  info->create(500, 300);
+  info->setBorderOn(1);
+  info->setTitleBarOn(1);
+
+  if (Video::getInstance()->getFormat() == Video::PAL)
+       info->setScreenPos(100, 180);
+  else
+       info->setScreenPos(100, 150);
+  char buf[INFOBUF];
+  char tbuf[Media::TIMEBUFLEN];
+  snprintf(buf,INFOBUF,"%s= %s\n%s= %ld x %ld\n%s= %ld kBytes\n%s= %s\n%s= %ld\n%s= 1/%ld",
+     tr("Directory"), parent->getDirname(),
+     tr("Format(px)"),jpeg.getJpegInfo(WJpeg::JPEG_WIDTH),jpeg.getJpegInfo(WJpeg::JPEG_HEIGHT),
+     tr("Filesize"),jpeg.getJpegInfo(WJpeg::JPEG_SIZE)/1000,
+     tr("Time"),currentMedia->getTimeString(tbuf),
+     tr("Rotation"),90*jpeg.getJpegInfo(WJpeg::JPEG_ROTATE),
+     tr("Scale"),jpeg.getJpegInfo(WJpeg::JPEG_SCALE));
+  info->setMainText(buf);
+  info->draw();
+  sendViewMsg(info,false);
+  Timers::getInstance()->setTimerD(this,3,8);
+}
+void VPicture::updateInfo(){
+  if (info) {
+    showInfo();
+    }
+}
+void VPicture::destroyInfo(bool fromTimer){
+  if (info) {
+    sendViewMsg(info,true);
+    info=NULL;
+    }
+  if (! fromTimer) Timers::getInstance()->cancelTimer(this,3);
+}
+
+void VPicture::sendViewMsg(View *v,bool vdestroy) {
+       Message* m = new Message(); 
+  m->message = vdestroy?Message::CLOSE_ME:Message::ADD_VIEW;
+  m->to = ViewMan::getInstance();
+  m->from = v;
+       m->parameter=(ULONG)v;
+  Command::getInstance()->postMessageFromOuterSpace(m);
+}
+void VPicture::sendCommandMsg(int command) {
+       Message* m = new Message(); 
+  m->message = Message::UDP_BUTTON;
+  m->to = Command::getInstance();
+  m->from = this;
+       m->parameter=command;
+  Command::getInstance()->postMessageFromOuterSpace(m);
+}
diff --git a/vpicture.h b/vpicture.h
new file mode 100644 (file)
index 0000000..3c8eaa0
--- /dev/null
@@ -0,0 +1,103 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef VPICTURE_H
+#define VPICTURE_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+
+#include "view.h"
+#include "wselectlist.h"
+#include "remote.h"
+#include "wsymbol.h"
+#include "viewman.h"
+#include "vdr.h"
+#include "media.h"
+#include "vvideolive.h"
+#include "colour.h"
+#include "video.h"
+#include "vinfo.h"
+#include "i18n.h"
+#include "vmedialist.h"
+#include "wjpeg.h"
+
+/**
+  * the picture viewer
+  * will ineract with the parent List for ff,back, slide show...
+  *
+*/
+class VPictureBanner;
+
+class VPicture : public View,public TimerReceiver
+{
+  public:
+    ~VPicture();
+
+    void processMessage(Message* m);
+    int handleCommand(int command);
+    void draw();
+    void timercall(int clientReference);
+    //factory method
+    //return NULL on error
+    static VPicture *createViewer(VMediaList * parent, bool startSlideshow=false);
+    //show the picture currently selected in the parent
+    //potentially moving to next/previous one
+    void showPicture(ULONG move=VMediaList::MV_NONE,int rotate=WJpeg::ROT_0);
+
+    //start a slideshow
+    void startSlideshow();
+
+
+  private:
+    VPicture(VMediaList * plist);
+    void showBanner(bool loading=false, int shortDisplayTime=0);
+    void destroyBanner(bool fromTimer=false);
+    void updateBanner(bool shortDisplay=false);
+    void showInfo();
+    void updateInfo();
+    void destroyInfo(bool fromTimer=false);
+               //add or destroy a view (banner, info)
+               void sendViewMsg(View *v,bool destroy=false);
+               //send command in main thread
+               void sendCommandMsg(int command);
+    VMediaList   *parent;
+    int  load(Media *m);
+    JpegReader   *reader;
+    bool needDraw;
+    WJpeg jpeg;
+    VPictureBanner *banner;
+    char * fullname;
+    char * filename;
+    ULONG ftime;
+    bool slideshow;
+    int showtime;
+    char * mediaError;
+    Media * currentMedia;
+    const static int INITIAL_SHOWTIME=5;
+    bool shortBanner;
+    int rotate;
+    VInfo * info;
+    static Colour pictureBack;
+    static Colour infoBack;
+};
+
+#endif
diff --git a/vpicturebanner.cc b/vpicturebanner.cc
new file mode 100644 (file)
index 0000000..13abc08
--- /dev/null
@@ -0,0 +1,136 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#include "vpicturebanner.h"
+#include "vpicture.h"
+
+
+VPictureBanner::VPictureBanner(VPicture *p, bool ld, bool sl)
+{
+  loading=ld;
+  slideshow=sl;
+  parent=p;
+  Video *v=Video::getInstance();
+  create(v->getScreenWidth()-100, 36);
+  setScreenPos(50, v->getScreenHeight()-50);
+  setBackgroundColour(Colour::VIEWBACKGROUND);
+  setTitleBarOn(0);
+  info=NULL;
+  Log::getInstance()->log("VPictureBanner",Log::DEBUG,"created %p",this);
+  //TODO compute sizes from text
+  rotsize=70;
+  infsize=50;
+}
+
+VPictureBanner::~VPictureBanner()
+{
+  Log::getInstance()->log("VPictureBanner",Log::DEBUG,"deleted %p",this);
+}
+
+
+
+void VPictureBanner::draw()
+{
+  View::draw();
+  if (loading) {
+    if (info) {
+      char buf[strlen(info)+100];
+      sprintf(buf,"%s %s",tr("Loading"),info);
+      drawText(buf,5,area.h-25,Colour::LIGHTTEXT);
+      }
+    else drawText(tr("Loading"),5,3,Colour::LIGHTTEXT);
+    }
+  else {
+    int x=5;
+    rectangle(x, area.h - 24, 18, 16, Colour::RED);
+    x+=18+3;
+    drawText(tr("rotate"), x, area.h - 25, Colour::LIGHTTEXT);
+    x+=rotsize+3;
+    rectangle(x, area.h - 24, 18, 16, Colour::GREEN);
+    x+=18+3;
+    drawText(tr("info"), 5+18+3+rotsize+3+18+3, area.h - 25, Colour::LIGHTTEXT);
+    x+=infsize+3;
+    WSymbol w;
+    w.setSurface(surface);
+    if (slideshow) {
+      w.nextSymbol = WSymbol::PAUSE;
+      }
+    else {
+      w.nextSymbol = WSymbol::PLAY;
+    }
+    w.setSurfaceOffset(x, area.h-24);
+    w.draw();
+    x+=20+3;
+    if (info) {
+      drawText(info,x,area.h - 25,Colour::LIGHTTEXT);
+    }
+  }
+}
+
+int VPictureBanner::handleCommand(int command)
+{
+  //don not handle commands - leave this to the picture viewer
+  return 0;
+}
+
+
+
+void VPictureBanner::processMessage(Message* m)
+{
+  if (m->message == Message::MOUSE_MOVE)
+  {
+    ;
+  }
+  else if (m->message == Message::MOUSE_LBDOWN)
+  {
+    //check if press is outside this view! then simulate cancel
+    int x=(m->parameter>>16)-getScreenX();
+    int y=(m->parameter&0xFFFF)-getScreenY();
+    if (x<0 || y <0 || x>getWidth() || y>getHeight())
+    {
+      ViewMan::getInstance()->handleCommand(Remote::BACK); //simulate cancel press
+    }
+    else if (y>=(int)area.h-24 && y<=(int)area.h-6)
+    {
+      //y coordinate is right!
+      if (x>=7 &&x<=25)
+      {
+        ViewMan::getInstance()->handleCommand(Remote::RED); //simulate red press
+      }
+      else if (x>=110 &&x<=128)
+      {
+        ViewMan::getInstance()->handleCommand(Remote::GREEN); //simulate red press
+      }
+    }
+  }
+}
+
+void VPictureBanner::setText(const char * text) {
+  if (info) delete info;
+  info=NULL;
+  if(!text) return;
+  info=new char[strlen(text)+1];
+  strcpy(info,text);
+}
+
+
+
+
+
diff --git a/vpicturebanner.h b/vpicturebanner.h
new file mode 100644 (file)
index 0000000..49f5d1c
--- /dev/null
@@ -0,0 +1,58 @@
+/*
+    Copyright 2004-2005 Chris Tallon, Andreas Vogel
+
+    This file is part of VOMP.
+
+    VOMP is free software; you can redistribute it and/or modify
+    it under the terms of the GNU General Public License as published by
+    the Free Software Foundation; either version 2 of the License, or
+    (at your option) any later version.
+
+    VOMP is distributed in the hope that it will be useful,
+    but WITHOUT ANY WARRANTY; without even the implied warranty of
+    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+    GNU General Public License for more details.
+
+    You should have received a copy of the GNU General Public License
+    along with VOMP; if not, write to the Free Software
+    Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA
+*/
+
+#ifndef VPICTUREBANNER_H
+#define VPICTUREBANNER_H
+
+#include <stdio.h>
+#include <string.h>
+#include <vector>
+#include <algorithm>
+
+#include "view.h"
+#include "remote.h"
+#include "colour.h"
+#include "video.h"
+#include "vinfo.h"
+#include "viewman.h"
+#include "i18n.h"
+
+class VPicture;
+
+class VPictureBanner : public View
+{
+  public:
+    VPictureBanner(VPicture *p,bool loading, bool slideshow);
+    ~VPictureBanner();
+    int handleCommand(int command);
+    void processMessage(Message* m);
+    void draw();
+    void setText(const char * text);
+    
+  private:
+    VPicture * parent;
+    char * info;
+    bool loading;
+    bool slideshow;
+    int rotsize;
+    int infsize;
+};
+
+#endif
index 7bfd9cb5b93f0b3b1592a9320b3b653a014197e2..b2bc219f6a730445edb5d90e7cf5b711802b49d0 100644 (file)
@@ -29,7 +29,7 @@ VWelcome::VWelcome()
   clockRegion.w = 60;
   clockRegion.h = 30;
 
-  create(460, 200);
+  create(460, 220);
   if (Video::getInstance()->getFormat() == Video::PAL)
   {
     setScreenPos(140, 170);
@@ -45,15 +45,16 @@ VWelcome::VWelcome()
 
   sl.setSurface(surface);
   sl.setSurfaceOffset(20, 40);
-  sl.setDimensions(170, 140);
+  sl.setDimensions(200, 160);
 
   setTitleText(tr("Welcome"));
   sl.addOption(tr("1. Live TV"), 1, 1);
   sl.addOption(tr("2. Radio"), 2, 0);
   sl.addOption(tr("3. Recordings"), 3, 0);
   sl.addOption(tr("4. Timers"), 4, 0);
-  sl.addOption(tr("5. Options"), 5, 0);
-  sl.addOption(tr("6. Reboot"), 6, 0);
+  sl.addOption(tr("5. MediaPlayer"), 5, 0);
+  sl.addOption(tr("6. Options"), 6, 0);
+  sl.addOption(tr("7. Reboot"), 7, 0);
 
   jpeg.setSurface(surface);
   jpeg.setSurfaceOffset(240, 60);
@@ -147,10 +148,15 @@ int VWelcome::handleCommand(int command)
     }
     case Remote::FIVE:
     {
-      doOptions();
+      doMediaList();
       return 2;
     }
     case Remote::SIX:
+    {
+      doOptions();
+      return 2;
+    }
+    case Remote::SEVEN:
     {
       Command::getInstance()->doReboot();
     }
@@ -179,10 +185,15 @@ int VWelcome::handleCommand(int command)
       }
       else if (option == 5)
       {
-        doOptions();
+        doMediaList();
         return 2;
       }
       else if (option == 6)
+      {
+        doOptions();
+        return 2;
+      }
+      else if (option == 7)
       {
         Command::getInstance()->doReboot();
         return 2;
@@ -262,6 +273,10 @@ void VWelcome::doRecordingsList()
   }
 }
 
+void VWelcome::doMediaList()
+{
+  VMediaList::createList();
+}
 void VWelcome::doTimersList()
 {
   VTimerList* vtl = new VTimerList();
index 320cc0f68a4276cd0dc29b40d5eadc54da33bbd9..56f7b146bcb083a47ccaa08eff693d73973ce6e2 100644 (file)
@@ -41,6 +41,7 @@
 #include "i18n.h"
 #include "timers.h"
 #include "vscreensaver.h"
+#include "vmedialist.h"
 
 class VWelcome : public View, public TimerReceiver
 {
@@ -65,6 +66,7 @@ class VWelcome : public View, public TimerReceiver
     void doTimersList();
     void doOptions();
     void drawClock();
+    void doMediaList();
 
     Region clockRegion;
 };
index 91805d6954710f8cf090a9609c2a904de0453090..4e750a041e7ee298124ba9825cc2832d4ca9cd08 100644 (file)
--- a/wjpeg.cc
+++ b/wjpeg.cc
 */
 
 #include "wjpeg.h"
+#include <setjmp.h>
+#include "i18n.h"
 
-int WJpeg::init(char* tfileName)
+
+extern "C" void
+jpeg_memio_src (j_decompress_ptr cinfo, void * userdata);
+extern "C" void
+jpeg_memio_cleanup (j_decompress_ptr cinfo);
+
+WJpeg::WJpeg(){
+  fileName=NULL;
+  reader=NULL;
+  useImageDimensions=true;
+  jheight=0;
+  jwidth=0;
+  jsize=0;
+  jerror=true;
+  rotate=0;
+  jscale=1;
+}
+
+WJpeg::~WJpeg() {
+  if (fileName) delete fileName;
+}
+
+int WJpeg::init(char* tfileName,bool useImage, JpegReader *rdr)
 {
-  fileName = tfileName;
+  rotate=0;
+  if (fileName) delete fileName;
+  fileName=NULL;
+  if (tfileName) {
+    fileName = new char[strlen(tfileName)+1];
+    strcpy(fileName,tfileName);
+  }
+  reader=rdr;
+  useImageDimensions=useImage;
   return 1;
 }
 
-void WJpeg::draw()
+
+ULONG WJpeg::getJpegInfo(ULONG tag){
+  switch(tag) {
+  case JPEG_HEIGHT:
+     return jheight;
+     break;
+  case JPEG_WIDTH:
+     return jwidth;
+     break;
+  case JPEG_SIZE:
+     return jsize;
+     break;
+  case JPEG_ROTATE:
+     return rotate;
+     break;
+  case JPEG_SCALE:
+     return jscale;
+     break;
+  }
+  return 0;
+}
+
+int WJpeg::getJpegInfo(ULONG tag, char * buffer) {
+  switch (tag) {
+  case JPEG_FILENAME:
+    strncpy(buffer,fileName,INFO_BUFFER-1);
+    buffer[INFO_BUFFER-1]=0;
+    return 0;
+    break;
+  }
+  return -1;
+}
+
+void WJpeg::setRotate(int amount) {
+  rotate=amount;
+}
+
+extern "C" {
+
+
+struct my_error_mgr {
+  struct jpeg_error_mgr pub;   /* "public" fields */
+  FILE *infile;                 /* to be used in error handler */
+
+  jmp_buf setjmp_buffer;       /* for return to caller */
+};
+
+typedef struct my_error_mgr * my_error_ptr;
+
+/*
+ * Here's the routine that will replace the standard error_exit method:
+ */
+
+METHODDEF(void)
+my_error_exit (j_common_ptr cinfo)
 {
-#ifndef WIN32
-  Log* logger = Log::getInstance();
+  /* cinfo->err really points to a my_error_mgr struct, so coerce pointer */
+  my_error_ptr myerr = (my_error_ptr) cinfo->err;
 
-  FILE* infile = fopen(fileName, "r");
-  if (infile == NULL)
-  {
-    logger->log("BJpeg", Log::ERR, "Can't open JPEG");
-    return;
+  /* Always display the message. */
+  /* We could postpone this until after returning, if we chose. */
+  (*cinfo->err->output_message) (cinfo);
+  if (myerr->infile) fclose(myerr->infile);
+  /* Return control to the setjmp point */
+  longjmp(myerr->setjmp_buffer, 1);
+}
+
+}
+
+bool WJpeg::hasError() {
+  return jerror;
+}
+void WJpeg::draw()
+{
+  jerror=false;
+  if (drawJpeg() != 0) {
+    jerror=true;
+    drawTextCentre(tr("Jpeg ERROR"), 240, 180, Colour::LIGHTTEXT);
   }
-  logger->log("BJpeg", Log::DEBUG, "File opened");
+}
 
+/* handle picture rotation
+   90: xr=h-y
+       yr=x
+   180:xr=w-x
+       yr=h-y
+   270:xr=y
+       yr=w-x
+*/
+void WJpeg::drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c){
+  int xb=0;
+  int yb=0;
+  switch(rotate) {
+     case ROT_0:
+        xb=x;
+        yb=y;
+        break;
+     case ROT_90:
+        xb=h-y;
+        yb=x;
+        break;
+     case ROT_180:
+        xb=w-x;
+        yb=h-y;
+        break;
+     case ROT_270:
+        xb=y;
+        yb=w-x;
+        break;
+     }
+   xb+=xpos;
+   yb+=ypos;
+   if (xb < 0 || yb < 0 ) {
+     Log::getInstance()->log("WJpeg:drawPixel",Log::ERR,"pixpos < 0 x=%d, y=%d",xb,yb);
+     return;
+   }
+   Box::drawPixel((UINT)xb,(UINT)yb,c);
+}
+
+int WJpeg::drawJpeg() {
+#ifndef WIN32
+  Log* logger = Log::getInstance();
+  unsigned char* buffer =NULL;
   struct jpeg_decompress_struct cinfo;
-  struct jpeg_error_mgr jerr;
-  cinfo.err = jpeg_std_error(&jerr);
+  struct my_error_mgr jerr;
+  jerr.infile=NULL;
+  cinfo.err = jpeg_std_error(&jerr.pub);
+  jerr.pub.error_exit = my_error_exit;
+  /* Establish the setjmp return context for my_error_exit to use. */
+  if (setjmp(jerr.setjmp_buffer)) {
+    /* If we get here, the JPEG code has signaled an error.
+     * We need to clean up the JPEG object, close the input file, and return.
+     */
+    if (reader) jpeg_memio_cleanup(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    logger->log("BJpeg", Log::ERR, "JPEG error");
+    if (buffer) free(buffer);
+    return 1;
+  }
+  int xpos=0;
+  int ypos=0;
   jpeg_create_decompress(&cinfo);
-  jpeg_stdio_src(&cinfo, infile);
+  if (fileName && ! reader) {
+    jsize=0; //TODO: compute size for local files
+    jerr.infile=fopen(fileName, "r");
+    if (jerr.infile == NULL)
+    {
+      logger->log("BJpeg", Log::ERR, "Can't open JPEG");
+      jpeg_destroy_decompress(&cinfo);
+      return 1;
+    }
+    logger->log("BJpeg", Log::DEBUG, "File opened");
+    jpeg_stdio_src(&cinfo, jerr.infile);
+  }
+  else if (reader) {
+     jsize=reader->initRead(fileName);
+     if (jsize <= 0) {
+        logger->log("BJpeg", Log::ERR, "Can't init JPEG transfer");
+        jpeg_destroy_decompress(&cinfo);
+        return 1;
+     }
+     jpeg_memio_src(&cinfo,(void *)reader);
+  }
+  else {
+     logger->log("BJpeg", Log::ERR, "neither  filename nor reader set");
+     jpeg_destroy_decompress(&cinfo);
+     return 1;
+  }
   jpeg_read_header(&cinfo, TRUE);
-  jpeg_start_decompress(&cinfo);
+  logger->log("BJpeg", Log::DEBUG, "JPEG read header w=%i h=%i, rot=%i", cinfo.image_width, cinfo.image_height, rotate);
+  int picturew=cinfo.image_width;
+  int pictureh=cinfo.image_height;
+  switch (rotate){
+    case ROT_90:
+    case ROT_270:
+      pictureh=cinfo.image_width;
+      picturew=cinfo.image_height;
+      break;
+  }
+  if (! useImageDimensions) {
+    //scale - we can have factors 1,2,4,8
+    int scalew=getWidth()*1000/picturew;
+    int scaleh=getHeight()*1000/pictureh;
+    int scale=scaleh;
+    if (scalew < scaleh) scale=scalew;
+    int fac=8;
+    //we allow for 10% bigger...
+    if (scale >= 900) fac=1;
+    else if (scale >= 450 ) fac=2;
+    else if (scale >= 225 ) fac=4;
+    cinfo.scale_denom=fac;
+    logger->log("BJpeg", Log::DEBUG, "JPEG scaling (1/1000) pw=%i ph=%i w=%i h=%i r=%i f=%i",
+       getWidth(),
+       getHeight(),
+       scalew,scaleh,scale,fac);
+    jscale=fac;
+    }
 
-  logger->log("BJpeg", Log::DEBUG, "JPEG startup done %i %i", cinfo.output_width, cinfo.output_height);
+  //remember picture parameters
+  jheight=pictureh;
+  jwidth=picturew;
 
-  // Init the surface
-  setDimensions(cinfo.output_width, cinfo.output_height);
+  jpeg_start_decompress(&cinfo);
+  //recompute width based on rotation (consider scale)
+  picturew=cinfo.output_width;
+  pictureh=cinfo.output_height;
+  switch (rotate){
+    case ROT_90:
+    case ROT_270:
+      pictureh=cinfo.output_width;
+      picturew=cinfo.output_height;
+      break;
+  }  
+  //if our image is smaller - center it
+  if (! useImageDimensions) {
+     xpos=(getWidth()-picturew)/2;
+     if (xpos <0) xpos=0;
+     ypos=(getHeight()-pictureh)/2;
+     if (ypos <0) ypos=0;
+  }
+  //center point for rotation
+  int w=cinfo.output_width;
+  int h=cinfo.output_height;
+  logger->log("BJpeg", Log::DEBUG, "JPEG startup done pw=%i ph=%i, xo=%i,yo=%i, iw=%i, ih=%i", picturew, pictureh,xpos,ypos,w,h);
 
+  // Init the surface
+  if (useImageDimensions) setDimensions(picturew, pictureh);
+  fillColour(backgroundColour);
 
+  //line len in bytes (3 bytes per Pixel) - for buffer (can be bigger then surface)
+  int linelen=cinfo.output_width*3;
+#ifdef USE_BUFFER
   // MAKE THE 2D ARRAY
+  buffer = (unsigned char*)malloc(config.output_height * linelen);
+  logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, cinfo.output_height, linelen);
+  if (buffer == NULL) {
+    if (reader) jpeg_memio_cleanup(&cinfo);
+    jpeg_destroy_decompress(&cinfo);
+    if (jerr.infile) fclose(jerr.infile);
+    logger->log("BJpeg", Log::ERR, "JPEG error - no room for buffer");
+    return 1;
+  }
+#endif
 
-  unsigned char* buffer = (unsigned char*)malloc(area.w * area.h * 3);
-  logger->log("BJpeg", Log::DEBUG, "Buffer allocated at %p, width = %i height = %i", buffer, area.w, area.h);
-
-  unsigned char* bufferPointers[area.h];
-  for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3);
-
-  logger->log("BJpeg", Log::DEBUG, "done array check");
+  //unsigned char* bufferPointers[area.h];
+  //for(UINT ps = 0; ps < area.h; ps++) bufferPointers[ps] = buffer + (ps * area.w * 3);
+  
+  logger->log("BJpeg", Log::DEBUG, "header w=%d,h=%d",cinfo.output_width,cinfo.output_height);
+#ifndef USE_BUFFER
+  unsigned char lbuf[linelen];
+  unsigned char * ptr=lbuf;
+#else
+  unsigned char * ptr=buffer;
+#endif
 
   int rowsread = 0;
+
+  Colour c;
   while (cinfo.output_scanline < cinfo.output_height)
   {
 //  logger->log("BJpeg", Log::DEBUG, "%i", rowsread);
-    rowsread += jpeg_read_scanlines(&cinfo, &bufferPointers[rowsread], area.h);
+    rowsread += jpeg_read_scanlines(&cinfo,&ptr,1);
+#ifdef USE_BUFFER
+    ptr+=linelen;
+#else
+     int x=0;
+     for (unsigned char * lp=ptr;lp < (ptr+linelen);lp+=3,x++) {
+       c.red=*lp ;
+       c.green=*(lp+1);
+       c.blue=*(lp+2);
+       drawPixel(x, rowsread-1,w,h,xpos,ypos, c);
+       }
+    
+#endif
   }
 
   logger->log("BJpeg", Log::DEBUG, "Done all jpeg_read");
 
   jpeg_finish_decompress(&cinfo);
+  if (reader) jpeg_memio_cleanup(&cinfo);
   jpeg_destroy_decompress(&cinfo);
 
-  fclose(infile);
+  if (jerr.infile) fclose(jerr.infile);
 
   logger->log("BJpeg", Log::DEBUG, "jpeg shutdown done, x, y %u %u", area.w, area.h);
-
+#ifdef USE_BUFFER
   Colour c;
   UINT x, y, xoff;
   unsigned char* p;
 
-  for (y = 0; y < area.h; y++)
+  for (y = 0; y < numlines; y++)
   {
     p = bufferPointers[y];
 
-    for (x = 0; x < area.w; x++)
+    for (x = 0; x < sfclen; x++)
     {
       xoff = x * 3;
 
@@ -100,11 +365,12 @@ void WJpeg::draw()
       c.green = p[xoff + 1];
       c.blue = p[xoff + 2];
 
-      drawPixel(x, y, c);
+      drawPixel(x, y, w,h,xpos,ypos,c);
     }
   }
 
   free(buffer);
+#endif
   logger->log("BJpeg", Log::DEBUG, "deleted buffer");
 #else
   DWORD width,height;
@@ -114,5 +380,229 @@ void WJpeg::draw()
 
   setDimensions(width, height);
 #endif
+  return 0;
+}
+
+
+extern "C" {
+ULONG jpeg_call_reader(ULONG offset,ULONG size,char ** buf,void *cb) {
+  JpegReader *rd=(JpegReader *)cb;
+  return rd->readChunk(offset,size,buf);
+}
+}
+//the memory buffer reader for the jpeg lib
+//taken from jdatasrc.c
+
+extern "C" {
+/* Expanded data source object for stdio input */
+#include "jinclude.h"
+#include "jpeglib.h"
+#include "jerror.h"/* Expanded data source object for stdio input */
+
+typedef struct {
+  struct jpeg_source_mgr pub;  /* public fields */
+
+  JOCTET * buffer;             /* start of buffer */
+  boolean start_of_file;       /* have we gotten any data yet? */
+  void * userdata;              /* used for callback */
+  ULONG offset;
+} my_source_mgr;
+
+typedef my_source_mgr * my_src_ptr;
+
+#define INPUT_BUF_SIZE  (64*4096)      /* choose an efficiently fread'able size */
+
+
+/*
+ * Initialize source --- called by jpeg_read_header
+ * before any data is actually read.
+ */
+
+METHODDEF(void)
+linit_source (j_decompress_ptr cinfo)
+{
+  my_src_ptr src = (my_src_ptr) cinfo->src;
+
+  /* We reset the empty-input-file flag for each image,
+   * but we don't clear the input buffer.
+   * This is correct behavior for reading a series of images from one source.
+   */
+  src->start_of_file = TRUE;
+  src->offset=0;
+}
+
+
+/*
+ * Fill the input buffer --- called whenever buffer is emptied.
+ *
+ * In typical applications, this should read fresh data into the buffer
+ * (ignoring the current state of next_input_byte & bytes_in_buffer),
+ * reset the pointer & count to the start of the buffer, and return TRUE
+ * indicating that the buffer has been reloaded.  It is not necessary to
+ * fill the buffer entirely, only to obtain at least one more byte.
+ *
+ * There is no such thing as an EOF return.  If the end of the file has been
+ * reached, the routine has a choice of ERREXIT() or inserting fake data into
+ * the buffer.  In most cases, generating a warning message and inserting a
+ * fake EOI marker is the best course of action --- this will allow the
+ * decompressor to output however much of the image is there.  However,
+ * the resulting error message is misleading if the real problem is an empty
+ * input file, so we handle that case specially.
+ *
+ * In applications that need to be able to suspend compression due to input
+ * not being available yet, a FALSE return indicates that no more data can be
+ * obtained right now, but more may be forthcoming later.  In this situation,
+ * the decompressor will return to its caller (with an indication of the
+ * number of scanlines it has read, if any).  The application should resume
+ * decompression after it has loaded more data into the input buffer.  Note
+ * that there are substantial restrictions on the use of suspension --- see
+ * the documentation.
+ *
+ * When suspending, the decompressor will back up to a convenient restart point
+ * (typically the start of the current MCU). next_input_byte & bytes_in_buffer
+ * indicate where the restart point will be if the current call returns FALSE.
+ * Data beyond this point must be rescanned after resumption, so move it to
+ * the front of the buffer rather than discarding it.
+ */
+
+METHODDEF(boolean)
+lfill_input_buffer (j_decompress_ptr cinfo)
+{
+  my_src_ptr src = (my_src_ptr) cinfo->src;
+  size_t nbytes;
+  if (src->buffer) free(src->buffer);
+  src->buffer=NULL;
+  nbytes = jpeg_call_reader(src->offset, INPUT_BUF_SIZE,(char **)&(src->buffer), src->userdata);
+
+  if (nbytes <= 0) {
+    WARNMS(cinfo, JWRN_JPEG_EOF);
+    src->buffer =  (JOCTET *)malloc(2);
+    src->buffer[0] = (JOCTET) 0xFF;
+    src->buffer[1] = (JOCTET) JPEG_EOI;
+    nbytes = 2;
+
+  }
+  src->offset+=nbytes;
+
+  src->pub.next_input_byte = src->buffer;
+  src->pub.bytes_in_buffer = nbytes;
+  src->start_of_file = FALSE;
+
+  return TRUE;
+}
+
+
+/*
+ * Skip data --- used to skip over a potentially large amount of
+ * uninteresting data (such as an APPn marker).
+ *
+ * Writers of suspendable-input applications must note that skip_input_data
+ * is not granted the right to give a suspension return.  If the skip extends
+ * beyond the data currently in the buffer, the buffer can be marked empty so
+ * that the next read will cause a fill_input_buffer call that can suspend.
+ * Arranging for additional bytes to be discarded before reloading the input
+ * buffer is the application writer's problem.
+ */
+
+METHODDEF(void)
+lskip_input_data (j_decompress_ptr cinfo, long num_bytes)
+{
+  my_src_ptr src = (my_src_ptr) cinfo->src;
+
+  /* Just a dumb implementation for now.  Could use fseek() except
+   * it doesn't work on pipes.  Not clear that being smart is worth
+   * any trouble anyway --- large skips are infrequent.
+   */
+  if (num_bytes > 0) {
+    while (num_bytes > (long) src->pub.bytes_in_buffer) {
+      num_bytes -= (long) src->pub.bytes_in_buffer;
+      (void) lfill_input_buffer(cinfo);
+      /* note we assume that fill_input_buffer will never return FALSE,
+       * so suspension need not be handled.
+       */
+    }
+    src->pub.next_input_byte += (size_t) num_bytes;
+    src->pub.bytes_in_buffer -= (size_t) num_bytes;
+  }
+}
+
+
+/*
+ * An additional method that can be provided by data source modules is the
+ * resync_to_restart method for error recovery in the presence of RST markers.
+ * For the moment, this source module just uses the default resync method
+ * provided by the JPEG library.  That method assumes that no backtracking
+ * is possible.
+ */
+
+
+/*
+ * Terminate source --- called by jpeg_finish_decompress
+ * after all data has been read.  Often a no-op.
+ *
+ * NB: *not* called by jpeg_abort or jpeg_destroy; surrounding
+ * application must deal with any cleanup that should happen even
+ * for error exit.
+ */
+
+METHODDEF(void)
+lterm_source (j_decompress_ptr cinfo)
+{
+  /* no work necessary here */
+}
+
+
+/*
+ * Prepare for input from a stdio stream.
+ * The caller must have already opened the stream, and is responsible
+ * for closing it after finishing decompression.
+ */
+
+extern "C" void
+jpeg_memio_src (j_decompress_ptr cinfo, void * userdata)
+{
+  my_src_ptr src;
+
+  /* The source object and input buffer are made permanent so that a series
+   * of JPEG images can be read from the same file by calling jpeg_stdio_src
+   * only before the first one.  (If we discarded the buffer at the end of
+   * one image, we'd likely lose the start of the next one.)
+   * This makes it unsafe to use this manager and a different source
+   * manager serially with the same JPEG object.  Caveat programmer.
+   */
+  if (cinfo->src == NULL) {    /* first time for this JPEG object? */
+    cinfo->src = (struct jpeg_source_mgr *)
+      (*cinfo->mem->alloc_small) ((j_common_ptr) cinfo, JPOOL_PERMANENT,
+                                 SIZEOF(my_source_mgr));
+    src = (my_src_ptr) cinfo->src;
+    src->buffer = NULL;
+  }
+
+  src = (my_src_ptr) cinfo->src;
+  src->pub.init_source = linit_source;
+  src->pub.fill_input_buffer = lfill_input_buffer;
+  src->pub.skip_input_data = lskip_input_data;
+  src->pub.resync_to_restart = jpeg_resync_to_restart; /* use default method */
+  src->pub.term_source = lterm_source;
+  src->userdata=userdata;
+  src->pub.bytes_in_buffer = 0; /* forces fill_input_buffer on first read */
+  src->pub.next_input_byte = NULL; /* until buffer loaded */
+  src->offset=0;
+  src->userdata=userdata;
+  if (src->buffer) {
+     free(src->buffer);
+     src->buffer=NULL;
+     }
+}
+/* cleanup to be called before cleanup of cinfo*/
+extern "C" void
+jpeg_memio_cleanup (j_decompress_ptr cinfo) {
+  my_src_ptr src=(my_src_ptr)cinfo->src;
+  Log::getInstance()->log("BJpeg", Log::DEBUG, "cleanup src, src=%p, buf=%p",src,(src?src->buffer:0));
+  if (src && src->buffer) {
+    free(src->buffer);
+    src->buffer=NULL;
+    }
+}
 }
 
diff --git a/wjpeg.h b/wjpeg.h
index ec2d78dc0845402adfa2c72496b03384408004ed..2e2a9c9fbb4d2601409ef12754406560e2343c9b 100644 (file)
--- a/wjpeg.h
+++ b/wjpeg.h
@@ -36,15 +36,68 @@ extern "C"
 #include "widget.h"
 #include "surface.h"
 
+//a reader to be implemented by the caller
+class JpegReader {
+  public:
+  //init reading the file
+  //return <=0 if error or size of image
+  virtual ULONG initRead(const char *filename)=0;
+  //read the next chunk of jpeg data
+  //offset - from start of file
+  //len I buf len (max bytes to read)
+  //return read len, 0 on EOF, -1 on error, *buf set to buffer
+  //will be released with free(!!!) after decoding
+  virtual ULONG readChunk(ULONG offset,ULONG len,char **buf)=0;
+  virtual ~JpegReader(){};
+};
+
 class WJpeg : public Widget
 {
   public:
-
-    int init(char* fileName);
+    WJpeg();
+    virtual ~WJpeg();
+    int init(char* fileName, bool useImage=true, JpegReader *rdr=NULL);
+    //rotate (with next draw!) by 90/180/270
+    void setRotate(int amount);
+    static const int ROT_0=0;
+    static const int ROT_90=1;
+    static const int ROT_180=2;
+    static const int ROT_270=3;
     void draw();
+    bool hasError();
+    /**
+      * get an integer info from the current picture
+      */
+    ULONG getJpegInfo(ULONG tag);
+    //in tags
+    static const ULONG JPEG_WIDTH=1;   //picture width
+    static const ULONG JPEG_HEIGHT=2;  //picture height
+    static const ULONG JPEG_SIZE=3;    //picture (compressed) size
+    static const ULONG JPEG_ROTATE=4;  //rotate setting
+    static const ULONG JPEG_SCALE=5;   //scale setting (1/n)
+    /**
+      * get a string info from the picture
+      * the user must provide a buffer of
+      * INFO_BUFFER size
+      */
+    int getJpegInfo(ULONG tag, char * buffer);
+    static const int INFO_BUFFER=200;
+    //string info tags
+    static const ULONG JPEG_FILENAME=100; //abbreviated filename
 
   private:
+    int drawJpeg();
+    //our drawPixel with considers rotation
+    void  drawPixel(int x, int y,int w,int h,int xpos, int ypos,Colour c);
     char* fileName;
+    JpegReader *reader;
+    bool useImageDimensions;
+    ULONG jsize;
+    ULONG jheight;
+    ULONG jwidth;
+    ULONG jscale;
+    bool jerror;
+    int rotate;
 };
 
 #endif