Logo Search packages:      
Sourcecode: vdr-plugin-xineliboutput version File versions  Download package

device.c

/*
 * device.c: xine-lib output device for the Video Disk Recorder
 *
 * See the main source file 'xineliboutput.c' for copyright information and
 * how to reach the author.
 *
 * $Id: device.c,v 1.37 2007/04/12 22:53:36 phintuka Exp $
 *
 */

#define __STDC_FORMAT_MACROS
#include <inttypes.h>

#include <vdr/config.h>
#include <vdr/thread.h>
#include <vdr/dvbspu.h>
#include <vdr/channels.h>
#include <vdr/skins.h>
#include <vdr/status.h>
#include <vdr/remote.h>

//#define XINELIBOUTPUT_DEBUG
//#define XINELIBOUTPUT_DEBUG_STDERR
//#define TRACK_EXEC_TIME
//#define SKIP_DVDSPU
//#define FORWARD_DVD_SPUS
//#define DEBUG_SWITCHING_TIME
//#define LOG_TRICKSPEED

#include "logdefs.h"
#include "config.h"
#include "osd.h"

#include "tools/listiter.h"
#include "tools/pes.h"
#include "tools/functor.h"

#include "frontend_local.h"
#include "frontend_svr.h"

#include "device.h"

#define STILLPICTURE_REPEAT_COUNT 3

//---------------------------- status monitor -------------------------------

class cXinelibStatusMonitor : public cStatus 
{
  private:
    cXinelibStatusMonitor(); 
    cXinelibStatusMonitor(cXinelibStatusMonitor&); 

  public:
    cXinelibStatusMonitor(cXinelibDevice& device, int cardIndex) :
        m_Device(device), m_cardIndex(cardIndex) 
    {
#ifdef DEBUG_SWITCHING_TIME
      switchtimeOff   = 0LL;
      switchtimeOn    = 0LL;
      switchingIframe = false;
#endif
    };

  protected:
    virtual void ChannelSwitch(const cDevice *Device, int ChannelNumber);
#if VDRVERSNUM < 10338
    virtual void Replaying(const cControl *Control, const char *Name);
#else
    virtual void Replaying(const cControl *Control, const char *Name, 
                     const char *FileName, bool On);
#endif

    cXinelibDevice& m_Device;
    int m_cardIndex;

#ifdef DEBUG_SWITCHING_TIME
  public:
    int64_t switchtimeOff;
    int64_t switchtimeOn;
    bool    switchingIframe;

    void IFrame(void)  
    {
      if(!switchingIframe) {
      int64_t now = cTimeMs::Now();
      switchingIframe = true;
      LOGMSG("Channel switch: off -> on %" PRId64 " ms, "
             "on -> 1. I-frame %" PRId64 " ms",
             switchtimeOn-switchtimeOff, now-switchtimeOn);
      } else {
      int64_t now = cTimeMs::Now();
      LOGMSG("Channel switch: on -> 2. I-frame %" PRId64 " ms, "
             "Total %" PRId64 " ms",
             now-switchtimeOn, now-switchtimeOff);
      switchtimeOff = 0LL;
      switchtimeOn = 0LL;
      switchingIframe = false;
      }
    }
#endif
};

void cXinelibStatusMonitor::ChannelSwitch(const cDevice *Device, 
                                int ChannelNumber) 
{
  TRACEF("cXinelibStatusMonitor::ChannelSwitch");
  TRACK_TIME(200);

  if (ChannelNumber) {
    if (Device->CardIndex() == m_cardIndex) {
#ifdef DEBUG_SWITCHING_TIME
      switchtimeOn = cTimeMs::Now();
#endif
      m_Device.SetTvMode(Channels.GetByNumber(ChannelNumber));
      TRACE("cXinelibStatusMonitor: Set to TvMode");
    }
  } else {
    if (Device->CardIndex() == m_cardIndex) {
#ifdef DEBUG_SWITCHING_TIME
      switchtimeOff = cTimeMs::Now();
#endif
      m_Device.StopOutput();
      TRACE("cXinelibStatusMonitor: received stop");
    }
  }
}

#if VDRVERSNUM < 10338
void cXinelibStatusMonitor::Replaying(const cControl *Control, 
                              const char *Name) 
{
  TRACEF("cXinelibStatusMonitor::Replaying");
  
  if (Name != NULL) {
    TRACE("cXinelibStatusMonitor: Replaying " << Name);
    m_Device.SetReplayMode();
  }
}
#else
void cXinelibStatusMonitor::Replaying(const cControl *Control, 
                              const char *Name,
                              const char *FileName, bool On)
{
  TRACEF("cXinelibStatusMonitor::Replaying");

  if (On /*&& Name != NULL*/) {
    TRACE("cXinelibStatusMonitor: Replaying " << Name << "(" << FileName << ")");
    m_Device.SetReplayMode();
  }
}
#endif

//----------------------------- device ----------------------------------------

//
// Singleton
//

cXinelibDevice* cXinelibDevice::m_pInstance = NULL;

cXinelibDevice& cXinelibDevice::Instance(void) 
{
  TRACEF("cXinelibDevice::Instance");
  if (!m_pInstance) {
    m_pInstance = new cXinelibDevice();
    TRACE("cXinelibDevice::Instance(): create, cardindex = " 
        << m_pInstance->CardIndex());
  }
  
  return *m_pInstance;
}

void cXinelibDevice::Dispose(void) 
{
  TRACEF("cXinelibDevice::Dispose");
  delete m_pInstance;
  m_pInstance = NULL;
}

//
// init and shutdown
//

cXinelibDevice::cXinelibDevice()
{
  TRACEF("cXinelibDevice::cXinelibDevice");

  m_statusMonitor = NULL;
  m_spuDecoder    = NULL;

  m_local  = NULL;
  m_server = NULL;

  m_OriginalPrimaryDevice = 0;
  m_ForcePrimaryDeviceCnt = 0;

  if(*xc.local_frontend && strncmp(xc.local_frontend, "none", 4))
    m_clients.Add(m_local = new cXinelibLocal(xc.local_frontend));
  if(xc.remote_mode && xc.listen_port>0)
    m_clients.Add(m_server = new cXinelibServer(xc.listen_port));

  m_ac3Present  = false;
  m_spuPresent  = false;

  m_CurrentDvdSpuTrack = -1;
  m_ForcedDvdSpuTrack = false;
  ClrAvailableDvdSpuTracks();

  memset(m_MetaInfo, 0, sizeof(m_MetaInfo));
    
  m_PlayMode = pmNone;
  m_LastTrack = ttAudioFirst;
  m_AudioChannel = 0;

  m_liveMode    = true;
  m_TrickSpeed  = -1;
  m_TrickSpeedPts = 0;
  m_TrickSpeedMode = 0;
  m_TrickSpeedDelay = 0;
  m_SkipAudio   = false;
  m_PlayingFile = false;
  m_StreamStart = true;
  m_RadioStream = false;
  m_AudioCount  = 0;
  m_FreeBufs = 0;
  m_Cleared = true;
}

cXinelibDevice::~cXinelibDevice() 
{
  TRACEF("cXinelibDevice::~cXinelibDevice");

  StopDevice();

  m_pInstance = NULL;
}

bool cXinelibDevice::StartDevice()
{
  TRACEF("cXinelibDevice::StartDevice");

  // if(dynamic_cast<cXinelibLocal*>(it))
  if(m_local) {
    int timer = 0;
    m_local->Start();
    while(!m_local->IsReady()) {
      cCondWait::SleepMs(100);
      if(m_local->IsFinished()) {
        LOGMSG("cXinelibDevice::Start(): Local frontend init failed");
        return false;
      }
      if(++timer >= 7*10) {
        LOGMSG("cXinelibDevice::Start(): Local frontend init timeout");
        return false;
      }
    }
    if(xc.force_primary_device)
      ForcePrimaryDevice(true);
  }

  if(m_server) {
    int timer = 0;
    m_server->Start();
    while(!m_server->IsReady()) {
      cCondWait::SleepMs(100);
      if(m_server->IsFinished()) {
        LOGMSG("cXinelibDevice::Start(): Server init failed");
        return false;
      }
      if(++timer >= 5*10) {
        LOGMSG("cXinelibDevice::Start(): Server init timeout");
        return false;
      }
    }
  }

  ASSERT(m_statusMonitor == NULL, false);
  m_statusMonitor = new cXinelibStatusMonitor(*this, CardIndex());

  LOGDBG("cXinelibDevice::StartDevice(): Device started");
  return true;
}

void cXinelibDevice::StopDevice(void) 
{
  TRACEF("cXinelibDevice::StopDevice");
  LOGDBG("cXinelibDevice::StopDevice(): Stopping device ...");

  if(m_statusMonitor) {
    delete m_statusMonitor;
    m_statusMonitor = NULL;
  }
  if (m_spuDecoder) {
    delete m_spuDecoder;
    m_spuDecoder = NULL;
  }

  cXinelibThread *server = m_server;
  cXinelibThread *local  = m_local;
  m_local = m_server = NULL;

  cControl::Shutdown();
  ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
  TrickSpeed(-1);

  if(local)  m_clients.Del(local,  false);
  if(server) m_clients.Del(server, false);

  if(server) {
    server->Stop();
    delete server;
  }
  if(local) {
    local->Stop();
    delete local;
  }
 
  m_clients.Clear();
}

//
// Primary device switching
//

void cXinelibDevice::MakePrimaryDevice(bool On) 
{
  TRACEF("cXinelibDevice::MakePrimaryDevice");

  if(On)
    new cXinelibOsdProvider(this);
}

void cXinelibDevice::ForcePrimaryDevice(bool On) 
{
  TRACEF("cXinelibDevice::ForcePrimaryDevice");

  m_MainThreadLock.Lock();
  m_MainThreadFunctors.Add(CreateFunctor(this, &cXinelibDevice::ForcePrimaryDeviceImpl, On));
  m_MainThreadLock.Unlock();
}

void cXinelibDevice::ForcePrimaryDeviceImpl(bool On) 
{
  TRACEF("cXinelibDevice::ForcePrimaryDeviceImpl");
  ASSERT(cThread::IsMainThread(), false);

  if(On) {
    m_ForcePrimaryDeviceCnt++;

    if(xc.force_primary_device) {
      if(cDevice::PrimaryDevice() && this != cDevice::PrimaryDevice()) {
      m_OriginalPrimaryDevice = cDevice::PrimaryDevice()->DeviceNumber() + 1;
      cControl::Shutdown();
      LOGMSG("Forcing primary device, original index = %d", m_OriginalPrimaryDevice);
      if(cOsd::IsOpen()) {
        LOGMSG("Forcing primary device, old OSD still open !");
#if VDRVERSNUM >= 10400
        xc.main_menu_mode = CloseOsd;
        cRemote::CallPlugin("xineliboutput");
#endif
      }
      SetPrimaryDevice(DeviceNumber() + 1);
      }
    }
  
  } else /* Off */ {
    m_ForcePrimaryDeviceCnt--;

    if(m_ForcePrimaryDeviceCnt < 0)
      LOGMSG("ForcePrimaryDevice: Internal error (ForcePrimaryDevice < 0)");
    else if(m_ForcePrimaryDeviceCnt == 0) {
      if(m_OriginalPrimaryDevice) {
      LOGMSG("Restoring original primary device %d", m_OriginalPrimaryDevice);
      cControl::Shutdown();
      if(cOsd::IsOpen()) {
        LOGMSG("Restoring primary device, xineliboutput OSD still open !");
#if VDRVERSNUM >= 10400
        xc.main_menu_mode = CloseOsd; /* will be executed in future by vdr main thread */
        cRemote::CallPlugin("xineliboutput");
#endif
      }
      cChannel *channel = Channels.GetByNumber(CurrentChannel());
      cDevice::SetPrimaryDevice(m_OriginalPrimaryDevice);
      PrimaryDevice()->SwitchChannel(channel, true);
      m_OriginalPrimaryDevice = 0;
      }
    }
  }
}

//
// Execute functors in main thread context
//

void cXinelibDevice::MainThreadHook(void)
{
  TRACEF("cXinelibDevice::MainThreadHook");

  if(m_MainThreadFunctors.First()) {
    cFunctor *f = NULL;
    do {
      m_MainThreadLock.Lock();
      if(f)
      m_MainThreadFunctors.Del(f);
      f = m_MainThreadFunctors.First();
      m_MainThreadLock.Unlock();
      
      if(f) {
      /*LOGDBG("cXinelibDevice::MainThreadHook: executing functor 0x%lx",(long)f);*/
      f->Execute();
      }

    } while(f);
  }
}

//
// Configuration
//

void cXinelibDevice::ConfigureOSD(bool prescale_osd, bool unscaled_osd)
{
  TRACEF("cXinelibDevice::ConfigureOSD");

  if(m_local)
    m_local->ConfigureOSD(prescale_osd, unscaled_osd);
  if(m_server)
    m_server->ConfigureOSD(prescale_osd, unscaled_osd);
}

void cXinelibDevice::ConfigurePostprocessing(const char *deinterlace_method, 
                                   int audio_delay, 
                                   int audio_compression, 
                                   const int *audio_equalizer, 
                                   int audio_surround,
                                   int speaker_type)
{
  TRACEF("cXinelibDevice::ConfigurePostprocessing");

  if(m_local)
    m_local->ConfigurePostprocessing(deinterlace_method, audio_delay, 
                             audio_compression, audio_equalizer,
                             audio_surround, speaker_type);
  if(m_server)
    m_server->ConfigurePostprocessing(deinterlace_method, audio_delay, 
                              audio_compression, audio_equalizer,
                              audio_surround, speaker_type);
}

void cXinelibDevice::ConfigurePostprocessing(const char *name, bool on, 
                                   const char *args)
{
  TRACEF("cXinelibDevice::ConfigurePostprocessing");

  if(m_local)
    m_local->ConfigurePostprocessing(name, on, args);
  if(m_server)
    m_server->ConfigurePostprocessing(name, on, args);
}

void cXinelibDevice::ConfigureVideo(int hue, int saturation, int brightness, int contrast,
                            int overscan)
{
  TRACEF("cXinelibDevice::ConfigureVideo");

  if(m_local)
    m_local->ConfigureVideo(hue, saturation, brightness, contrast, overscan);
  if(m_server)
    m_server->ConfigureVideo(hue, saturation, brightness, contrast, overscan);
}

void cXinelibDevice::ConfigureDecoder(int pes_buffers)
{
  TRACEF("cXinelibDevice::ConfigureDecoder");

  if(m_local)
    m_local->ConfigureDecoder(pes_buffers);
  //if(m_server)
  //  m_server->ConfigureDecoder(pes_buffers);

  cXinelibOsdProvider::RefreshOsd();
}

void cXinelibDevice::ConfigureWindow(int fullscreen, int width, int height, 
                             int modeswitch, const char *modeline, 
                             int aspect, int scale_video, 
                             int field_order)
{
  TRACEF("cXinelibDevice::ConfigureWindow");

  if((!*xc.local_frontend || !strncmp(xc.local_frontend, "none", 4)) && m_local) {
    cXinelibThread *tmp = m_local;
    m_clients.Del(tmp, false);
    m_local = NULL;
    cCondWait::SleepMs(5);
    tmp->Stop();
    cCondWait::SleepMs(5);
    delete tmp;
    if(xc.force_primary_device)
      ForcePrimaryDevice(false);
  }

  if(m_local)
    m_local->ConfigureWindow(fullscreen, width, height, modeswitch, modeline, 
                       aspect, scale_video, field_order);

  else if(*xc.local_frontend && strncmp(xc.local_frontend, "none", 4)) {
    cXinelibThread *tmp = new cXinelibLocal(xc.local_frontend);
    tmp->Start();
    m_clients.Add(m_local = tmp);

    cCondWait::SleepMs(25);
    while(!m_local->IsReady() && !m_local->IsFinished())
      cCondWait::SleepMs(25);

    if(m_local->IsFinished()) {
      m_local = NULL;
      m_clients.Del(tmp, true);
      Skins.QueueMessage(mtError, tr("Frontend initialization failed"), 10);
    } else {
      if(xc.force_primary_device)
      ForcePrimaryDevice(true);
  
      m_local->ConfigureWindow(fullscreen, width, height, modeswitch, modeline,
                         aspect, scale_video, field_order);
    }
  }
}

void cXinelibDevice::Listen(bool activate, int port)
{
  TRACEF("cXinelibDevice::Listen");

  if(activate && port>0) {
    if(!m_server) {
      cXinelibThread *tmp = new cXinelibServer(port);
      tmp->Start();
      m_clients.Add(m_server = tmp);

      cCondWait::SleepMs(10);
      while(!m_server->IsReady() && !m_server->IsFinished())
      cCondWait::SleepMs(10);

      if(m_server->IsFinished()) {
      Skins.QueueMessage(mtError, tr("Server initialization failed"), 10);
      m_server = NULL;
      m_clients.Del(tmp, true);
      }

    } else {
      if(! m_server->Listen(port))
      Skins.QueueMessage(mtError, tr("Server initialization failed"), 10);
    }
  } else if( /*((!activate) || port<=0) && */ m_server) {
    cXinelibThread *tmp = m_server;
    m_clients.Del(tmp, false);
    m_server = NULL;
    cCondWait::SleepMs(5);
    tmp->Stop();
    cCondWait::SleepMs(5);
    delete tmp;
  }
}

//
// OSD
//

void cXinelibDevice::OsdCmd(void *cmd)
{
  TRACEF("cXinelibDevice::OsdCmd");
  TRACK_TIME(250);

  if(m_server)    // call first server, local frontend modifies contents of the message ...
    m_server->OsdCmd(cmd);
  if(m_local)
    m_local->OsdCmd(cmd);
}

//
// Play mode control
//
void cXinelibDevice::StopOutput(void)
{
  TRACEF("cXinelibDevice::StopOutput");
  TRACK_TIME(250);

  m_RadioStream = false;
  m_AudioCount  = 0;
  ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
  Clear();
  ForEach(m_clients, &cXinelibThread::QueueBlankDisplay);
  ForEach(m_clients, &cXinelibThread::SetNoVideo, false);
  ClrAvailableDvdSpuTracks();
  m_ForcedDvdSpuTrack = false;
}

void cXinelibDevice::SetTvMode(cChannel *Channel)
{
  TRACEF("cXinelibDevice::SetTvMode");
  TRACK_TIME(250);

  m_RadioStream = false;
  if (Channel && !Channel->Vpid() && (Channel->Apid(0) || Channel->Apid(1)))
    m_RadioStream = true;
  if(m_PlayMode == pmAudioOnlyBlack)
    m_RadioStream = true;
  TRACE("cXinelibDevice::SetTvMode - isRadio = "<<m_RadioStream);

  m_StreamStart = true;
  m_liveMode = true;
  m_TrickSpeed = -1;
  m_SkipAudio  = false;
  m_AudioCount = 0;
  m_spuPresent = false;

  Clear();
  ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
  ForEach(m_clients, &cXinelibThread::SetLiveMode, true);
  ForEach(m_clients, &cXinelibThread::ResumeOutput);
}

void cXinelibDevice::SetReplayMode(void)
{
  TRACEF("cXinelibDevice::SetReplayMode");

  m_RadioStream = true; // first seen replayed video packet resets this
  m_AudioCount  = 15;
  m_StreamStart = true;

  ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
  Clear();
  ForEach(m_clients, &cXinelibThread::SetNoVideo, false /*m_RadioStream*/);
  if(m_RadioStream && !m_liveMode)
    ForEach(m_clients, &cXinelibThread::BlankDisplay);
  ForEach(m_clients, &cXinelibThread::ResumeOutput);

  m_liveMode = false;
}

bool cXinelibDevice::SetPlayMode(ePlayMode PlayMode) 
{
  TRACEF("cXinelibDevice::SetPlayMode");

#ifdef XINELIBOUTPUT_DEBUG
  switch (PlayMode) {
    case pmNone:     
      TRACE("cXinelibDevice::SetPlayMode audio/video from decoder"); break;
    case pmAudioVideo: 
      TRACE("cXinelibDevice::SetPlayMode audio/video from player"); break;
    case pmVideoOnly:  
      TRACE("cXinelibDevice::SetPlayMode video from player, audio from decoder"); break;
    case pmAudioOnly:  
      TRACE("cXinelibDevice::SetPlayMode audio from player, video from decoder"); break;
    case pmAudioOnlyBlack: 
      TRACE("cXinelibDevice::SetPlayMode audio only from player, no video (black screen)"); break;
    case pmExtern_THIS_SHOULD_BE_AVOIDED: 
      TRACE("cXinelibDevice::SetPlayMode this should be avoided"); break;
  }
#endif

  m_ac3Present = false;
  m_spuPresent = false;

  ClrAvailableDvdSpuTracks();
  m_PlayMode = PlayMode;

  TrickSpeed(-1);
  if (m_PlayMode == pmAudioOnlyBlack) {
    TRACE("pmAudioOnlyBlack --> BlankDisplay, NoVideo");
    ForEach(m_clients, &cXinelibThread::BlankDisplay);
    ForEach(m_clients, &cXinelibThread::SetNoVideo, true);
  } else {
    if(m_liveMode)
      ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
    else
      ForEach(m_clients, &cXinelibThread::SetNoVideo, 
            m_RadioStream && (m_AudioCount<1));
    Clear();
  }
  
  return true;
}

//
// Playback control
//

// m_TrickSpeedMode flags
#define trs_IPB_frames 0x01  // stream has all frame types
#define trs_I_frames   0x02  // stream has only I-frames
#define trs_PTS_recalc 0x04  // PTS must be re-calculated
#define trs_NoAudio    0x08  // no audio in trick speed mode
#define trs_PTS_check  0x80  // detect in PlayVideo if PTS must be recalculated

void cXinelibDevice::TrickSpeed(int Speed) 
{
  TRACEF("cXinelibDevice::TrickSpeed");

  if(m_TrickSpeed != Speed) {
    int RealSpeed = abs(Speed);
#ifdef LOG_TRICKSPEED
    int PrevSpeed = m_TrickSpeed;
    LOGMSG("TrickSpeed changed from %d to %d [%d]", PrevSpeed, Speed, RealSpeed);
#endif
    m_TrickSpeedPts = 0;
    m_TrickSpeed = Speed;
    m_TrickSpeedDelay = 0;

    //  Possible transitions:
    //     fast <-> play
    //     play <-> pause
    //     pause <-> slow
    //     _and_ from any mode to normal play and pause

    if(Speed==8 || Speed==4 || Speed == 2) {
#ifdef LOG_TRICKSPEED
      LOGMSG("    Slow forward (1/%d speed), IPB-frames", Speed);
#endif
      /* do nothing - slow forward is just slow playback of complete stream */
      m_TrickSpeedMode = trs_IPB_frames;

      /* previous state was slow forward or pause --> no need for clear */

      /* change decoder and UDP/RTP scheduler clock rates */
      ForEach(m_clients, &cXinelibThread::TrickSpeed, RealSpeed);
    }

    else if(Speed == 63 || Speed == 48 || Speed == 24) {
      RealSpeed = (Speed+11)/12;
#ifdef LOG_TRICKSPEED
      LOGMSG("    Slow backward (1/%d speed), I-frames only", RealSpeed);    
#endif
      /* previous state was slow backwards or pause --> clear if it was pause */
      //if(PrevSpeed == 0 && !(m_TrickSpeedMode & trs_PTS_recalc)) {
      //  LOGMSG("    -> Clear");
      //  ForEach(m_clients, &cXinelibThread::Clear);
      //}

      /* only I-frames, backwards, pts must be re-generated */
      m_TrickSpeedMode = trs_I_frames | trs_PTS_recalc | trs_NoAudio;

      /* change decoder and UDP/RTP scheduler clock rates */
      ForEach(m_clients, &cXinelibThread::TrickSpeed, RealSpeed);
    }

    else if(Speed == 6 || Speed == 3 || Speed == 1) {
      RealSpeed = 12/Speed;
#ifdef LOG_TRICKSPEED
      LOGMSG("    Fast (%dx speed), direction unknown, I-frames only", RealSpeed);
#endif
      // TODO: patch dvbplayer to always use P and B frames too ... -> smoother speeds 3 and 6

      /* only I-frames, backwards, pts must be re-generated if playing backwards */
      m_TrickSpeedMode |= trs_PTS_check;

      /* backward/forward state is unknown until first PTS is seen 
       so, clear() must be done in PlayVideo. */
      /* previous trick speed state is not overwritten yet ... ! */
      
      /* change decoder and UDP/RTP scheduler clock rates */
      ForEach(m_clients, &cXinelibThread::TrickSpeed, -RealSpeed);
    }

    else if(Speed==-1 || Speed == 0) {
#ifdef LOG_TRICKSPEED
      LOGMSG("    Play/Pause");
#endif
      /* change decoder and UDP/RTP scheduler clock rates */
      ForEach(m_clients, &cXinelibThread::TrickSpeed, RealSpeed);

      /* returning from backward mode needs Clear */
      //if(Speed==-1 && (m_TrickSpeedMode & trs_PTS_recalc)) {
      //  LOGMSG("    -> Clear");
      //  ForEach(m_clients, &cXinelibThread::Clear);
      //  m_TrickSpeedMode = 0;
      //}
      /* returning from fast forward mode needs Clear
         because of DvbPlayer jumps few seconds back at mode change ... */
      //if(Speed==-1 && (m_TrickSpeedMode & trs_I_frames)) {
      //  LOGMSG("    -> Clear");
      //  ForEach(m_clients, &cXinelibThread::Clear);
      //}
      m_TrickSpeedMode = 0;
    } 

    else {
      LOGMSG("    Unknown trickspeed %d !", Speed);
      m_TrickSpeedMode = 0;
      m_TrickSpeed = -1;
      ForEach(m_clients, &cXinelibThread::TrickSpeed, -1);
    }
  }
}

void cXinelibDevice::Clear(void) 
{
  TRACEF("cXinelibDevice::Clear");
  TRACK_TIME(100);

  if(m_Cleared && m_StreamStart && m_TrickSpeed == -1) {
    //LOGMSG("************ Double Clear ***************");
  } else {
    //LOGMSG("************ FIRST  Clear ***************");
    m_Cleared = true;
    m_StreamStart = true;
    m_FreeBufs = 0;
    TrickSpeed(-1);
    ForEach(m_clients, &cXinelibThread::Clear);
    ForEach(m_clients, &cXinelibThread::SetStillMode, false);
  }
}

void cXinelibDevice::Play(void) 
{
  TRACEF("cXinelibDevice::Play");

  m_SkipAudio  = false;
  
  ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
  TrickSpeed(-1);
}

void cXinelibDevice::Freeze(void) 
{
  TRACEF("cXinelibDevice::Freeze");

  TrickSpeed(0);
}

int64_t cXinelibDevice::GetSTC(void)
{
  TRACEF("cXinelibDevice::GetSTC");

  if(m_local)
    return m_local->GetSTC();
  if(m_server)
    return m_server->GetSTC();
  return cDevice::GetSTC();
}

bool cXinelibDevice::Flush(int TimeoutMs) 
{
  TRACEF("cXinelibDevice::Flush");
  TRACK_TIME(500);

  if(m_TrickSpeed == 0) {
    ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
    TrickSpeed(-1);
  }

  bool r = ForEach(m_clients, &cXinelibThread::Flush, TimeoutMs, 
               &mand<bool>, true);

  return r;
}


//
// Playback of files and images
//

int cXinelibDevice::PlayFileCtrl(const char *Cmd)
{
  TRACEF("cXinelibDevice::PlayFile");
  int result = -1;

  if(m_PlayingFile) {
    if(m_server)
      result = m_server->PlayFileCtrl(Cmd);
    if(m_local) 
      result = m_local->PlayFileCtrl(Cmd);
  }
  return result;
}

bool cXinelibDevice::EndOfStreamReached(void)
{
  if(m_local && !m_local->EndOfStreamReached())
    return false;
  if(m_server && !m_server->EndOfStreamReached())
    return false;
  return true;
}

bool cXinelibDevice::PlayFile(const char *FileName, int Position, bool LoopPlay)
{
  TRACEF("cXinelibDevice::PlayFile");
  TRACE("cXinelibDevice::PlayFile(\"" << FileName << "\")");

  bool result = true;

  if(FileName) {
    if(!m_PlayingFile) {
      m_PlayingFile = true;
      StopOutput();
    }
    for(int i = 0; i < mi_Count; i++) 
      m_MetaInfo[i][0] = 0;
    if(m_server)
      result = m_server->PlayFile(FileName, Position, LoopPlay);
    if(m_local)
      result = m_local->PlayFile(FileName, Position, LoopPlay);
  } else if(/*!FileName &&*/m_PlayingFile) {
    if(m_server) 
      result = m_server->PlayFile(NULL, 0);
    if(m_local)
      result = m_local->PlayFile(NULL, 0);
    if(!m_liveMode)
      SetReplayMode();
    else
      SetTvMode(Channels.GetByNumber(cDevice::CurrentChannel()));
    m_PlayingFile = false;
  }

  return result;
}

//
// Data stream handling
//

int cXinelibDevice::PlayTrickSpeed(const uchar *buf, int length) 
{
  if(abs(m_TrickSpeed) > 1 && (m_TrickSpeedMode & trs_I_frames)) {
    uchar PictureType = NO_PICTURE;
    if(ScanVideoPacket(buf, length, PictureType) > 0) {
#ifdef LOG_TRICKSPEED
      if(PictureType != NO_PICTURE) {
      bool  Video = false, Audio = false;
      int64_t pts = pes_extract_pts(buf, length, Audio, Video);
      LOGMSG("    TrickSpeed: frame %s pts %lld", PictureTypeStr(PictureType), pts);
      }
#endif
    }

#if 1 
    // limit I-frame rate
    if(PictureType == I_FRAME) {
      static int64_t t0 = 0;
      int64_t t1 = cTimeMs::Now();
      if((t1 - t0) < 1000) {
      int fdelay = 40*12;
      if(m_TrickSpeed==6)  fdelay /= 2;
      if(m_TrickSpeed==3)  fdelay /= 4;
      if(m_TrickSpeed==1)  fdelay /= 12;
      if(m_TrickSpeed==63) fdelay *= 6;
      if(m_TrickSpeed==48) fdelay *= 4;
      if(m_TrickSpeed==24) fdelay *= 2;

      /* wait if data is coming in too fast */
      if(fdelay - (t1-t0) >= 40) {
        m_TrickSpeedDelay = 40;
        return -1;
      }
      
      t0 += fdelay;

      pes_strip_pts((uchar*)buf, length);
      } else {
      t0 = t1;
      }
    }
#endif
  }

  //
  // detecting trick speed mode ?
  //
  if( m_TrickSpeed > 0 && (m_TrickSpeedMode & trs_PTS_check)) {
    bool Video = false, Audio = false;
    int64_t pts = pes_extract_pts(buf, length, Audio, Video);
    if(Video && pts > 0) {
      if(m_TrickSpeedPts == 0) {
      m_TrickSpeedMode |= trs_NoAudio;
      m_TrickSpeedPts = pts;
#ifdef LOG_TRICKSPEED
      LOGMSG("    Seen video pts = %lld", m_TrickSpeedPts);
#endif
      } else {
      if(pts < m_TrickSpeedPts) {
        /* -> playing fast backwards */
#ifdef LOG_TRICKSPEED
        LOGMSG("    Detected fast backward mode. last %lld now %lld", 
             m_TrickSpeedPts, pts);
#endif
        //if(!(m_TrickSpeedMode & trs_PTS_recalc))
        //  ForEach(m_clients, &cXinelibThread::Clear);
        m_TrickSpeedMode = trs_I_frames | trs_PTS_recalc | trs_NoAudio;   
      } else {
#ifdef LOG_TRICKSPEED
        LOGMSG("    Detected fast forward mode");
#endif
        m_TrickSpeedMode = trs_I_frames;
      }
      }
    }
  }

  //
  // Trick speed mode with PTS re-calc
  //
  if( m_TrickSpeed > 0 && (m_TrickSpeedMode & trs_PTS_recalc)) {
    bool Video = false, Audio = false;
    int64_t pts = pes_extract_pts(buf, length, Audio, Video);

    if(Video && pts>0) {
#ifdef LOG_TRICKSPEED
      LOGMSG("    pts %lld -> %lld (diff %lld)", pts, 
           m_TrickSpeedPts + 40*12*90, m_TrickSpeedPts + 40*12*90 - pts);
#endif
      pts = m_TrickSpeedPts = m_TrickSpeedPts + 40*12*90; /* 12 frames * 40ms -> pts units */
      pts ^= 0x80000000; /* discontinuity (when mode changes) forces re-syncing of all clocks */
      pes_change_pts((uchar*)buf, length, pts);
    }
    if(Audio) {
      LOGMSG("PlayTrickSpeed: got audio frame !");
    }
  }

#if 1
  else if(m_TrickSpeedMode & trs_I_frames) {
    bool Video = false, Audio = false;
    int64_t pts = pes_extract_pts(buf, length, Audio, Video);
    if(Video && pts>0) {
      pts ^= 0x80000000; /* discontinuity (when mode changes) forces re-syncing of all clocks */
      pes_change_pts((uchar*)buf, length, pts);
    }
  }
#endif

  return 0;
}

int cXinelibDevice::PlayAny(const uchar *buf, int length) 
{
  TRACEF("cXinelibDevice::PlayAny");
  TRACK_TIME(100);

  if(m_PlayingFile)
    return length;

  //
  // Need to be sure Poll has been called for every frame:
  //  - cDevice can feed multiple frames after each poll from player/transfer.
  //  - If only part of frames are consumed, rest are fed again after next Poll.
  //  - If there are multiple clients it is possible first client(s)
  //    can queue more frames than last client(s).
  //    -> frame(s) are either lost immediately (last client(s)) 
  //       or duplicated after next poll (first client(s))
  //
  if(m_FreeBufs < 1) {
    cPoller Poller;
    if(!Poll(Poller,0)) {
      errno = EAGAIN;
      return 0;
    }
  }

  bool isMpeg1 = false;

  int len = pes_packet_len(buf, length, isMpeg1);
  if(len>0 && len != length) 
    LOGMSG("cXinelibDevice::PlayAny: invalid data !");

  if(m_TrickSpeed > 0) {
    if(PlayTrickSpeed(buf, length) < 0)
      return 0; /* wait if data is coming in too fast */
  } else if(m_SkipAudio) {
    /* needed for still images when moving cutting marks */
    pes_strip_pts((uchar*)buf, length);
  }
  m_Cleared = false;
  m_FreeBufs --;

  if(m_local) {
    length = (isMpeg1 ? m_local->Play_Mpeg1_PES(buf,length) : 
                      m_local->Play_PES(buf,length));
  } 
  if(m_server && length > 0) {
    int length2 = isMpeg1 ? m_server->Play_Mpeg1_PES(buf, length) : 
                            m_server->Play_PES(buf, length);   
    if(!m_local)
      return length2;
  }
  
  return length;
}

int cXinelibDevice::PlayVideo(const uchar *buf, int length) 
{
  TRACEF("cXinelibDevice::PlayVideo");
  TRACK_TIME(100);

  if(m_PlayMode == pmAudioOnlyBlack)
    return length;

  if(m_RadioStream) {
    m_RadioStream = false;
    m_AudioCount  = 0;
    ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
  }
  
  if(m_StreamStart) {

#ifdef START_IFRAME
    // Start with I-frame if stream has video
    // wait for first I-frame
    uchar pictureType;
    if( ScanVideoPacket(buf, length, /*0,*/pictureType) > 0 && 
      pictureType == I_FRAME) {
      m_StreamStart = false;
    } else {
      return length;
    }
#endif

    int Width, Height;
    if(GetVideoSize(buf, length, &Width, &Height)) {
      m_StreamStart = false;
      //LOGDBG("Detected video size %dx%d", Width, Height);
      ForEach(m_clients, &cXinelibThread::SetHDMode, (Width > 800));
    }
  }

#ifdef DEBUG_SWITCHING_TIME
  if(m_statusMonitor->switchtimeOff && m_statusMonitor->switchtimeOn) {
    uchar pictureType;
    if( ScanVideoPacket(buf, length, pictureType) > 0 && 
      pictureType == I_FRAME)
      m_statusMonitor->IFrame();
  }
#endif

  return PlayAny(buf, length);
}

void cXinelibDevice::StillPicture(const uchar *Data, int Length) 
{
  TRACEF("cXinelibDevice::StillPicture");

  // skip still images coming in too fast (ex. when moving cutting marks)
  if(cRemote::HasKeys()) {
    static int skipped = 0;
    static uint64_t lastshow = 0;
    uint64_t now = cTimeMs::Now();
    if(now - lastshow < 500) {
      skipped++;
      //LOGMSG("Skipping still image (coming in too fast)");
      return;
    }
    LOGDBG("Forcing still image - skipped %d images", skipped);
    lastshow = now;
    skipped = 0;
  }

  bool isPes   = (!Data[0] && !Data[1] && Data[2] == 0x01 && 
              (Data[3] & 0xF0) == 0xE0);
  bool isMpeg1 = isPes && ((Data[6] & 0xC0) != 0x80);
  int i;

  if(m_PlayingFile)
    return;

  TRACE("cXinelibDevice::StillPicture: isPes = "<<isPes
      <<", isMpeg1 = "<<isMpeg1);

  ForEach(m_clients, &cXinelibThread::Clear);
  ForEach(m_clients, &cXinelibThread::SetNoVideo, false);
  ForEach(m_clients, &cXinelibThread::SetLiveMode, false);
  ForEach(m_clients, &cXinelibThread::SetStillMode, true);
  ForEach(m_clients, &cXinelibThread::TrickSpeed, 1);

  m_TrickSpeed = -1; // to make Poll work ...
  m_SkipAudio = 1;   // enables audio and pts stripping

  for(i=0; i<STILLPICTURE_REPEAT_COUNT; i++)
    if(isMpeg1) {
      ForEach(m_clients, &cXinelibThread::Play_Mpeg1_PES, Data, Length, 
            &mmin<int>, Length);
    } else if(isPes) {
      /*cDevice::*/PlayPes(Data, Length, m_SkipAudio);
    } else {
      ForEach(m_clients, &cXinelibThread::Play_Mpeg2_ES, 
            Data, Length, VIDEO_STREAM,
            &mand<bool>, true);
    }

  // creates empty video PES with pseudo-pts
  ForEach(m_clients, &cXinelibThread::Play_Mpeg2_ES,
        Data, 0, VIDEO_STREAM,
        &mand<bool>, true);

  ForEach(m_clients, &cXinelibThread::Flush, 60, 
        &mand<bool>, true);

  m_TrickSpeed = 0;
  m_SkipAudio = 0;
}

int cXinelibDevice::PlayAudio(const uchar *buf, int length, uchar Id) 
{
  TRACEF("cXinelibDevice::PlayAudio");
  TRACK_TIME(100);

  if(!buf || length < 6)
    return length;

#ifdef SKIP_AC3_AUDIO
    // skip AC3 audio
  if(((unsigned char *)buf)[3] == PRIVATE_STREAM1) {
    TRACE("cXinelibDevice::PlayVideo: PRIVATE_STREAM1 discarded");
    return length;
  }
#endif

  // strip audio in trick speed modes and when displaying still images
  if(m_SkipAudio /*|| m_TrickSpeed > 0*/)
    return length;
  if(m_TrickSpeedMode & trs_NoAudio)
    return length;

  if(m_RadioStream) {
    if(m_AudioCount) {
      m_AudioCount--;
      if(!m_AudioCount) {
      LOGDBG("PlayAudio detected radio stream");
      ForEach(m_clients, &cXinelibThread::SetNoVideo, m_RadioStream);
      }
    }
  }

  return PlayAny(buf, length);
}

int cXinelibDevice::PlaySpu(const uchar *buf, int length, uchar Id) 
{
  TRACEF("cXinelibDevice::PlaySpu");

#ifdef SKIP_DVDSPU
  return length;
#else
  if(!buf || length < 6)
    return length;

  if(((unsigned char *)buf)[3] == PRIVATE_STREAM1) {

    if(!m_spuPresent) {
      TRACE("cXinelibDevice::PlaySpu first DVD SPU frame");
      Skins.QueueMessage(mtInfo,"DVD Subtitles");
      m_spuPresent = true;
      
      ForEach(m_clients, &cXinelibThread::SpuStreamChanged, (int)Id);
    }

    // Strip all but selected SPU track
    if(Id != m_CurrentDvdSpuTrack)
      return length;
  }

  return PlayAny(buf, length);
#endif
}

bool cXinelibDevice::Poll(cPoller &Poller, int TimeoutMs) 
{
  TRACEF("cXinelibDevice::Poll");
  TRACK_TIME(400);

  if(m_PlayingFile)
    return true;

  if(m_TrickSpeed == 0) {
    cCondWait::SleepMs(min(TimeoutMs, 20));
    return Poller.Poll(0);
  }

  if(!m_local && !m_server) {
    /* nothing to do... why do I exist ... ? */
    //cCondWait::SleepMs(TimeoutMs);
    //return Poller.Poll(0);
    return true;
  }

  if(m_TrickSpeed > 1 && m_TrickSpeedDelay > 20) {
    cCondWait::SleepMs(20);
    m_TrickSpeedDelay -= 20;
    return false;
  }

  if(m_FreeBufs < 1) {
    int result = DEFAULT_POLL_SIZE;

    if(m_local)
      result = min(result, m_local->Poll(Poller, TimeoutMs));
    if(m_server)
      result = min(result, m_server->Poll(Poller, TimeoutMs));

    m_FreeBufs = max(result, 0);
  }
  
  return m_FreeBufs > 0 /*|| Poller.Poll(0)*/;
}

//
// Audio facilities
//

void cXinelibDevice::SetVolumeDevice(int Volume) 
{
  TRACEF("cXinelibDevice::SetVolumeDevice");

  ForEach(m_clients, &cXinelibThread::SetVolume, Volume);  
}

void cXinelibDevice::SetAudioTrackDevice(eTrackType Type)
{
  TRACEF("cXinelibDevice::SetAudioTrackDevice");

  // track changes are autodetected at xine side
}

void cXinelibDevice::SetAudioChannelDevice(int AudioChannel)
{
  TRACEF("cXinelibDevice::SetAudioChannelDevice");

  if(m_AudioChannel != AudioChannel) {
    m_AudioChannel = AudioChannel;
    //LOGDBG("cXinelibDevice::SetAudioChannelDevice --> %d", AudioChannel);
#if 0
    switch(AudioChannel) {
      default:
    //case 0: ConfigurePostprocessing("upmix_mono", false, NULL);
      case 0: ConfigurePostprocessing("upmix_mono", true, "channel=-1");
              break;
      case 1: ConfigurePostprocessing("upmix_mono", true, "channel=0");
            break;
      case 2: ConfigurePostprocessing("upmix_mono", true, "channel=1");
            break;
    }
#else
    switch(AudioChannel) {
      default:
      case 0: ConfigurePostprocessing("audiochannel", false, NULL); 
              break;
      case 1: ConfigurePostprocessing("audiochannel", true, "channel=0");
            break;
      case 2: ConfigurePostprocessing("audiochannel", true, "channel=1");
            break;
    }
#endif
  }
}

void cXinelibDevice::SetDigitalAudioDevice(bool On)
{
  TRACEF("cXinelibDevice::SetDigitalAudioDevice");

  // track changes are autodetected at xine side
}

//
// Video format facilities
//

void cXinelibDevice::SetVideoFormat(bool VideoFormat16_9) 
{
  TRACEF("cXinelibDevice::SetVideoFormat");
  cDevice::SetVideoFormat(VideoFormat16_9);

#if 0
  //
  // TODO
  //
  if(xc.aspect != ASPECT_AUTO && 
     xc.aspect != ASPECT_DEFAULT) {
    if(VideoFormat16_9)
      xc.aspect = ASPECT_16_9;
    else if(xc.aspect == ASPECT_16_9)
      xc.aspect = ASPECT_4_3;
    ConfigureDecoder(,,,xc.aspect,,,);
  }
#endif
}

void cXinelibDevice::SetVideoDisplayFormat(eVideoDisplayFormat VideoDisplayFormat)
{
  TRACEF("cXinelibDevice::SetVideoDisplayFormat");
  cDevice::SetVideoDisplayFormat(VideoDisplayFormat);

#if 0
  //
  // TODO
  //
  //  - set normal, pan&scan, letterbox (only for 4:3?)
  //
  if(xc.aspect != ASPECT_AUTO && 
     xc.aspect != ASPECT_DEFAULT)  {
    switch(VideoDisplayFormat) {
    case vdfPanAndScan:
      xc.aspect = ASPECT_PAN_SCAN;
      break;
    case vdfLetterBox:    
      xc.aspect = ASPECT_4_3; /* borders are added automatically if needed */
      break;
    case vdfCenterCutOut:
      xc.aspect = ASPECT_CENTER_CUT_OUT;
      break;
    }
    ConfigureDecoder(,,,xc.aspect,,,);
  }
#endif
}

eVideoSystem cXinelibDevice::GetVideoSystem(void)
{
  TRACEF("cXinelibDevice::GetVideoSystem");
  return cDevice::GetVideoSystem();
}


//
// SPU decoder
//

#ifdef FORWARD_DVD_SPUS
# include "spu_decoder.h"
#endif

cSpuDecoder *cXinelibDevice::GetSpuDecoder(void)
{
  TRACEF("cXinelibDevice::GetSpuDecoder");
  if (!m_spuDecoder && IsPrimaryDevice()) {
    //
    // TODO
    //
    //  - use own derived SpuDecoder with special cXinelibOsd 
    //    -> always visible
    //

#ifdef FORWARD_DVD_SPUS
    // forward DVD SPUs to xine without decoding
    m_spuDecoder = new cFwdSpuDecoder(this);
#else
    m_spuDecoder = new cDvbSpuDecoder();
#endif
  }
  return m_spuDecoder;
}

//
// Image Grabbing
//

#if VDRVERSNUM < 10338

bool cXinelibDevice::GrabImage(const char *FileName, bool Jpeg, 
                         int Quality, int SizeX, int SizeY)
{
  uchar *Data = NULL;
  int Size = 0;
  TRACEF("cXinelibDevice::GrabImage");

  if(m_local)
    Data = m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
  if(!Data && m_server)
    Data = m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);

  if(Data) {
    FILE *fp = fopen(FileName, "wb");
    if(fp) {
      fwrite(Data, Size, 1, fp);
      fclose(fp);
      free(Data);     
      return true;
    } 
    LOGERR("Grab: Can't open %s", FileName);
    free(Data);
  } else {
    LOGMSG("Grab to %s failed", FileName);
  }
  return false;
}

#else

uchar *cXinelibDevice::GrabImage(int &Size, bool Jpeg, 
                         int Quality, int SizeX, int SizeY)
{
  TRACEF("cXinelibDevice::GrabImage");

  if(m_local)
    return m_local->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);
  if(m_server)
    return m_server->GrabImage(Size, Jpeg, Quality, SizeX, SizeY);

  return NULL;
}

#endif


//
// DVD SPU support in VDR recordings
//
//   - override cDevice::PlayPesPacket to get DVD SPUs
//

int cXinelibDevice::PlayPesPacket(const uchar *Data, int Length, 
                          bool VideoOnly)
{
#ifndef SKIP_DVDSPU
  switch (Data[3]) {
    case 0xBD: { // private stream 1
      int PayloadOffset = Data[8] + 9;
      uchar SubStreamId = Data[PayloadOffset];
      uchar SubStreamType = SubStreamId & 0xF0;
      uchar SubStreamIndex = SubStreamId & 0x1F;
      switch (SubStreamType) {
        case 0x20: // SPU
        case 0x30: // SPU
        SetAvailableDvdSpuTrack(SubStreamIndex);
        return PlaySpu(Data, Length, SubStreamIndex);
        break;
        default:
        ;
      }
    }
    default:
      ;
  }
#endif
  return cDevice::PlayPesPacket(Data, Length, VideoOnly);
}


//
// Available DVD SPU tracks
//

bool cXinelibDevice::SetCurrentDvdSpuTrack(int Type, bool Force)
{
  if(Type == -1 || 
     ( Type >= 0 && 
       Type < 64 &&
       m_DvdSpuTrack[Type].id != 0xffff)) {
    m_CurrentDvdSpuTrack = Type;
    ForEach(m_clients, &cXinelibThread::SpuStreamChanged, Type);
    if(Force)
      m_ForcedDvdSpuTrack = true;
    return true;
  }
  return false;
}

void cXinelibDevice::ClrAvailableDvdSpuTracks(bool NotifyFrontend)
{
  for(int i=0; i<64; i++)
    m_DvdSpuTrack[i].id = 0xffff;
  if(m_CurrentDvdSpuTrack >=0 ) {
    m_CurrentDvdSpuTrack = -1;
    if(NotifyFrontend)
      ForEach(m_clients, &cXinelibThread::SpuStreamChanged, -1);
  }
}

int cXinelibDevice::NumDvdSpuTracks(void) const
{ 
  int DvdSpuTracks = 0;
  for(int i=0; i<64; i++)
    if(m_DvdSpuTrack[i].id != 0xffff)
      DvdSpuTracks++;
  return DvdSpuTracks; 
}

const tTrackId *cXinelibDevice::GetDvdSpuTrack(int Type) const
{
  if(Type >= 0 && Type < 64 &&
     m_DvdSpuTrack[Type].id != 0xffff)
    return &m_DvdSpuTrack[Type];
  return NULL;
}

const char *cXinelibDevice::GetDvdSpuLang(int Type) const
{
  const tTrackId *track = GetDvdSpuTrack(Type);
  if(track)
    return track->language[0] ? track->language : NULL;
  return NULL;
}

bool cXinelibDevice::SetAvailableDvdSpuTrack(int Type, const char *lang, bool Current)
{
  if(Type >= 0 && Type < 64) {

    m_DvdSpuTrack[Type].id = Type;
    m_DvdSpuTrack[Type].language[0] = '\0';
    if(lang) 
      strn0cpy(m_DvdSpuTrack[Type].language, lang, MAXLANGCODE2);
    if(Current)
      m_CurrentDvdSpuTrack = Type;

    return true;
  }
  return false;
}

void cXinelibDevice::EnsureDvdSpuTrack(void)
{
  if(!m_ForcedDvdSpuTrack &&
     NumDvdSpuTracks() > 0 &&
     (m_DvdSpuTrack[0].id == 0xffff ||
      strcmp(m_DvdSpuTrack[0].language, "menu"))) {

    if(xc.spu_autoshow) {
      int pref, len, track;
      for(pref = 0; pref < 4; pref++)
      for(track = 0; track < 64; track++)
        if(m_DvdSpuTrack[track].id != 0xffff)    
          if((len=strlen(xc.spu_lang[pref])) > 0)
            //if(!strncmp(m_DvdSpuTrack[track].language,
            //            xc.spu_lang[pref], len)) {
            if(!strcmp(m_DvdSpuTrack[track].language,
                   xc.spu_lang[pref])) {
            if(m_CurrentDvdSpuTrack != track) {
              LOGMSG("Auto-selecting %d. SPU track \'%s\'  (%d. preferred language is \'%s\')", 
                   track, m_DvdSpuTrack[track].language, pref+1, xc.spu_lang[pref]);
              cXinelibDevice::SetCurrentDvdSpuTrack(track);
              cString msg = cString::sprintf("Subtitles: %s", m_DvdSpuTrack[track].language);
              Skins.QueueMessage(mtInfo, msg);
            }
            m_spuPresent = true;
            track = 64;
            pref = 99;
            }
    }

    if(!m_spuPresent) {
      Skins.QueueMessage(mtInfo, "Subtitles present");
      m_spuPresent = true;
    }
  }
}

//
// Metainfo
//

const char *cXinelibDevice::GetMetaInfo(eMetainfoType Type)
{
  if(Type >= 0 && Type < mi_Count)
    return m_MetaInfo[Type];

  LOGMSG("cXinelibDevice::GetMetaInfo: unknown metainfo type");
  return "";
}

void  cXinelibDevice::SetMetaInfo(eMetainfoType Type, const char *Value)
{
  if(Type >= 0 && Type < mi_Count) {
    /* set to 0 first, so if player is accessing string in middle of 
       copying it will always be 0-terminated (but truncated) */
    memset(m_MetaInfo[Type], 0, sizeof(m_MetaInfo[Type]));
    strn0cpy(m_MetaInfo[Type], Value, MAX_METAINFO_LEN);
  } else {
    LOGMSG("cXinelibDevice::SetMetaInfo: unknown metainfo type");
  }
}


Generated by  Doxygen 1.6.0   Back to index