diff -Naur vdr-1.5.8.org/config.c vdr-1.5.8/config.c --- vdr-1.5.8.org/config.c 2007-08-12 08:09:37.000000000 -0400 +++ vdr-1.5.8/config.c 2008-01-23 17:09:36.394978598 -0500 @@ -250,6 +250,15 @@ PauseLifetime = 1; UseSubtitle = 1; UseVps = 0; + LiveBuffer = 0; + LiveBufferSize = 10; + LiveReplay = 0; + KeepBuffer = 0; + KeepLastBuffer = 0; + NumLastBuffers = 1; + BufferTimeOut = 30; + MinLiveBufferInactivity = 0; + Frameswait = 0; VpsMargin = 120; RecordingDirs = 1; VideoDisplayFormat = 1; @@ -421,6 +430,15 @@ else if (!strcasecmp(Name, "UseSubtitle")) UseSubtitle = atoi(Value); else if (!strcasecmp(Name, "UseVps")) UseVps = atoi(Value); else if (!strcasecmp(Name, "VpsMargin")) VpsMargin = atoi(Value); + else if (!strcasecmp(Name, "LiveBuffer")) LiveBuffer = atoi(Value); + else if (!strcasecmp(Name, "LiveBufferSize")) LiveBufferSize = atoi(Value); + else if (!strcasecmp(Name, "LiveReplay")) LiveReplay = atoi(Value); + else if (!strcasecmp(Name, "KeepBuffer")) KeepBuffer = atoi(Value); + else if (!strcasecmp(Name, "KeepLastBuffer")) KeepLastBuffer = atoi(Value); + else if (!strcasecmp(Name, "NumLastBuffers")) NumLastBuffers = atoi(Value); + else if (!strcasecmp(Name, "BufferTimeOut")) BufferTimeOut = atoi(Value); + else if (!strcasecmp(Name, "MinLiveBufferInactivity")) MinLiveBufferInactivity = atoi(Value); + else if (!strcasecmp(Name, "Frameswait")) Frameswait = atoi(Value); else if (!strcasecmp(Name, "RecordingDirs")) RecordingDirs = atoi(Value); else if (!strcasecmp(Name, "VideoDisplayFormat")) VideoDisplayFormat = atoi(Value); else if (!strcasecmp(Name, "VideoFormat")) VideoFormat = atoi(Value); @@ -498,6 +516,15 @@ Store("UseSubtitle", UseSubtitle); Store("UseVps", UseVps); Store("VpsMargin", VpsMargin); + Store("LiveBuffer", LiveBuffer); + Store("LiveBufferSize", LiveBufferSize); + Store("LiveReplay", LiveReplay); + Store("KeepBuffer", KeepBuffer); + Store("KeepLastBuffer", KeepLastBuffer); + Store("NumLastBuffers", NumLastBuffers); + Store("BufferTimeOut", BufferTimeOut); + Store("MinLiveBufferInactivity", MinLiveBufferInactivity); + Store("Frameswait", Frameswait); Store("RecordingDirs", RecordingDirs); Store("VideoDisplayFormat", VideoDisplayFormat); Store("VideoFormat", VideoFormat); diff -Naur vdr-1.5.8.org/config.h vdr-1.5.8/config.h --- vdr-1.5.8.org/config.h 2007-08-12 16:38:10.000000000 -0400 +++ vdr-1.5.8/config.h 2008-01-23 17:09:36.395978671 -0500 @@ -36,6 +36,8 @@ // plugins to work with newer versions of the core VDR as long as no // VDR header files have changed. +#define LIVEBUFFERVERSION 108 + #define MAXPRIORITY 99 #define MAXLIFETIME 99 @@ -233,6 +235,15 @@ int UseSubtitle; int UseVps; int VpsMargin; + int LiveBuffer; + int LiveBufferSize; + int LiveReplay; + int KeepBuffer; + int KeepLastBuffer; + int NumLastBuffers; + int BufferTimeOut; + int MinLiveBufferInactivity; + int Frameswait; int RecordingDirs; int VideoDisplayFormat; int VideoFormat; diff -Naur vdr-1.5.8.org/cutter.c vdr-1.5.8/cutter.c --- vdr-1.5.8.org/cutter.c 2006-07-30 06:22:08.000000000 -0400 +++ vdr-1.5.8/cutter.c 2008-01-23 17:09:36.395978671 -0500 @@ -12,6 +12,8 @@ #include "remux.h" #include "thread.h" #include "videodir.h" +#include "recorder.h" +#include "menu.h" // --- cCuttingThread -------------------------------------------------------- @@ -22,17 +24,19 @@ cFileName *fromFileName, *toFileName; cIndexFile *fromIndex, *toIndex; cMarks fromMarks, toMarks; + cFileWriter *writer; protected: virtual void Action(void); public: - cCuttingThread(const char *FromFileName, const char *ToFileName); + cCuttingThread(const char *FromFileName, const char *ToFileName, cFileWriter *Writer); virtual ~cCuttingThread(); const char *Error(void) { return error; } }; -cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName) +cCuttingThread::cCuttingThread(const char *FromFileName, const char *ToFileName, cFileWriter *Writer) :cThread("video cutting") { + writer=Writer; error = NULL; fromFile = toFile = NULL; fromFileName = toFileName = NULL; @@ -77,6 +81,7 @@ uchar buffer[MAXFRAMESIZE]; bool LastMark = false; bool cutIn = true; + bool lIFrame = false; while (Running()) { uchar FileNumber; int FileOffset, Length; @@ -88,7 +93,14 @@ // Read one frame: + if (writer && Index>=fromIndex->Last()) + continue; if (fromIndex->Get(Index++, &FileNumber, &FileOffset, &PictureType, &Length)) { + if (Length==0) + { + Index++; + continue; + } if (FileNumber != CurrentFileNumber) { fromFile = fromFileName->SetOffset(FileNumber, FileOffset); fromFile->SetReadAhead(MEGABYTE(20)); @@ -116,6 +128,17 @@ // Write one frame: if (PictureType == I_FRAME) { // every file shall start with an I_FRAME + if (writer) + if (!lIFrame) + { + if (fromIndex->GetLast() - Index <= 1) + { + writer->RecordNextIFrame(((u_int64_t)(buffer[9]&0x0E)<<29)+((u_int64_t)buffer[10]<<22)+((u_int64_t)(buffer[11]&0xFE)<<14)+((u_int64_t)buffer[12]<<7)+((u_int64_t)(buffer[13]&0xFE)>>1)); + lIFrame=true; + } + } + else + break; if (LastMark) // edited version shall end before next I-frame break; if (FileSize > MEGABYTE(Setup.MaxVideoFileSize)) { @@ -168,6 +191,13 @@ } } else + if (writer) + { + Index = fromIndex->GetNextIFrame(fromIndex->Last(),false); + CurrentFileNumber = 0; + cutIn = true; + } + else LastMark = true; } } @@ -183,15 +213,27 @@ cCuttingThread *cCutter::cuttingThread = NULL; bool cCutter::error = false; bool cCutter::ended = false; +cLiveRecorder *cCutter::liveRecorder = NULL; -bool cCutter::Start(const char *FileName) +bool cCutter::Start(const char *FileName, cLiveRecorder *LiveRecorder, bool IsBeginning, cTimer *Timer) { if (!cuttingThread) { + liveRecorder=LiveRecorder; + if (liveRecorder) + liveRecorder->ChangePriority(100); error = false; ended = false; cRecording Recording(FileName); - const char *evn = Recording.PrefixFileName('%'); - if (evn && RemoveVideoFile(evn) && MakeDirs(evn, true)) { + const char *evn = Recording.PrefixFileName('%',LiveRecorder != NULL); + if (Timer && !IsBeginning) + { + cRecordControl *temp = new cRecordControl(NULL,Timer,false,false,true); + evn = strdup(temp->FileName()); + delete temp; + } + if (IsBeginning) + cRecordControls::Start(Timer,false,&evn); + if (evn && (IsBeginning || RemoveVideoFile(evn)) && MakeDirs(evn, true)) { // XXX this can be removed once RenameVideoFile() follows symlinks (see videodir.c) // remove a possible deleted recording with the same name to avoid symlink mixups: char *s = strdup(evn); @@ -207,7 +249,7 @@ editedVersionName = strdup(evn); Recording.WriteInfo(); Recordings.AddByName(editedVersionName, false); - cuttingThread = new cCuttingThread(FileName, editedVersionName); + cuttingThread = new cCuttingThread(FileName, editedVersionName, IsBeginning ? cRecordControls::GetRecordControl(evn)->GetWriter() : NULL); return true; } } @@ -230,11 +272,14 @@ } } -bool cCutter::Active(void) +bool cCutter::Active(bool *needsLiveBuffer) { if (cuttingThread) { - if (cuttingThread->Active()) + if (cuttingThread->Active()) { + if (needsLiveBuffer) + *needsLiveBuffer = liveRecorder != NULL; return true; + } error = cuttingThread->Error(); Stop(); if (!error) @@ -255,6 +300,11 @@ bool cCutter::Ended(void) { + if (liveRecorder) + { + liveRecorder->ChangePriority(-1); + liveRecorder=NULL; + } bool result = ended; ended = false; return result; diff -Naur vdr-1.5.8.org/cutter.h vdr-1.5.8/cutter.h --- vdr-1.5.8.org/cutter.h 2002-06-22 06:03:15.000000000 -0400 +++ vdr-1.5.8/cutter.h 2008-01-23 17:09:36.396978743 -0500 @@ -10,6 +10,9 @@ #ifndef __CUTTER_H #define __CUTTER_H +#include "timers.h" +#include "transfer.h" + class cCuttingThread; class cCutter { @@ -18,10 +21,11 @@ static cCuttingThread *cuttingThread; static bool error; static bool ended; + static cLiveRecorder *liveRecorder; public: - static bool Start(const char *FileName); + static bool Start(const char *FileName, cLiveRecorder *LiveRecorder = NULL, bool IsBeginning = false, cTimer *Timer = NULL); static void Stop(void); - static bool Active(void); + static bool Active(bool *needsLiveBuffer = NULL); static bool Error(void); static bool Ended(void); }; diff -Naur vdr-1.5.8.org/device.c vdr-1.5.8/device.c --- vdr-1.5.8.org/device.c 2008-01-23 17:07:56.972779994 -0500 +++ vdr-1.5.8/device.c 2008-01-23 17:09:36.397978816 -0500 @@ -18,6 +18,8 @@ #include "receiver.h" #include "status.h" #include "transfer.h" +#include "cutter.h" +#include "videodir.h" // --- cPesAssembler --------------------------------------------------------- @@ -172,6 +174,7 @@ ClrAvailableTracks(); currentAudioTrack = ttNone; currentAudioTrackMissingCount = 0; + currentchannel = 0; for (int i = 0; i < MAXRECEIVERS; i++) receiver[i] = NULL; @@ -612,6 +615,12 @@ return false; } +bool cDevice::ProvidesChannelAndLiveBuffer(const cChannel *Channel, int Priority, bool *NeedsDetachReceivers, bool LiveRec) const +{ + return false; +} + + bool cDevice::IsTunedToTransponder(const cChannel *Channel) { return false; @@ -674,12 +683,22 @@ eSetChannelResult cDevice::SetChannel(const cChannel *Channel, bool LiveView) { - if (LiveView) + bool needsLiveBuffer; + if (LiveView && cCutter::Active(&needsLiveBuffer)) + if (needsLiveBuffer && !(Setup.KeepBuffer || Setup.KeepLastBuffer)) { + Skins.Message(mtError, tr("LiveBuffer is still needed, try again later!")); + return scrFailed; + } + cLiveRecorderControl::UpdateTimes(); + if (!Setup.LiveBuffer) + cLiveRecorderControl::Shutdown(); + + if (LiveView && !(Setup.LiveBuffer && cLiveRecorderControl::HasProgramme())) StopReplay(); cDevice *Device = (LiveView && IsPrimaryDevice()) ? GetDevice(Channel, 0, LiveView) : this; - bool NeedsTransferMode = Device != this; + bool NeedsTransferMode = Device != this || Setup.LiveBuffer && LiveView && IsPrimaryDevice(); eSetChannelResult Result = scrOk; @@ -690,7 +709,7 @@ if (Device && CanReplay()) { cStatus::MsgChannelSwitch(this, 0); // only report status if we are actually going to switch the channel if (Device->SetChannel(Channel, false) == scrOk) // calling SetChannel() directly, not SwitchChannel()! - cControl::Launch(new cTransferControl(Device, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); + Setup.LiveBuffer ? cLiveRecorderControl::Add(Device, Channel) : cControl::Launch(new cTransferControl(Device, Channel->GetChannelID(), Channel->Vpid(), Channel->Apids(), Channel->Dpids(), Channel->Spids())); else Result = scrNoTransfer; } @@ -725,6 +744,8 @@ } if (Result == scrOk) { + if (!NeedsTransferMode) + currentchannel = Channel->Number(); if (LiveView && IsPrimaryDevice()) { currentChannel = Channel->Number(); // Set the available audio tracks: @@ -765,7 +786,7 @@ bool cDevice::HasProgramme(void) { - return Replaying() || pidHandles[ptAudio].pid || pidHandles[ptVideo].pid; + return Replaying() || cLiveRecorderControl::HasProgramme() || pidHandles[ptAudio].pid || pidHandles[ptVideo].pid; } int cDevice::GetAudioChannelDevice(void) @@ -1191,10 +1212,16 @@ int cDevice::Priority(void) const { int priority = IsPrimaryDevice() ? Setup.PrimaryLimit - 1 : DEFAULTPRIORITY; + int livebuffer = -100; for (int i = 0; i < MAXRECEIVERS; i++) { - if (receiver[i]) + if (receiver[i]) { + if (receiver[i]->priority<=-2) + livebuffer=max(receiver[i]->priority,livebuffer); priority = max(receiver[i]->priority, priority); } + } + if (priority < Setup.PrimaryLimit && livebuffer != -100) + priority = livebuffer; return priority; } @@ -1206,7 +1233,7 @@ bool cDevice::Receiving(bool CheckAny) const { for (int i = 0; i < MAXRECEIVERS; i++) { - if (receiver[i] && (CheckAny || receiver[i]->priority >= 0)) // cReceiver with priority < 0 doesn't count + if (receiver[i] && (CheckAny || receiver[i]->priority >= 0 || receiver[i]->priority<=-2)) // cReceiver with priority -1 doesn't count return true; } return false; diff -Naur vdr-1.5.8.org/device.h vdr-1.5.8/device.h --- vdr-1.5.8.org/device.h 2007-07-22 07:20:13.000000000 -0400 +++ vdr-1.5.8/device.h 2008-01-23 17:09:36.411979829 -0500 @@ -194,6 +194,7 @@ protected: static int currentChannel; + int currentchannel; public: virtual bool ProvidesSource(int Source) const; ///< Returns true if this device can provide the given source. @@ -219,6 +220,7 @@ ///< function itself actually returns true. ///< The default implementation always returns false, so a derived cDevice ///< class that can provide channels must implement this function. + virtual bool ProvidesChannelAndLiveBuffer(const cChannel *Channel, int Priority = -1, bool *NeedsDetachReceivers = NULL, bool LiveRec = false) const; virtual bool IsTunedToTransponder(const cChannel *Channel); ///< Returns true if this device is currently tuned to the given Channel's ///< transponder. diff -Naur vdr-1.5.8.org/dvbdevice.c vdr-1.5.8/dvbdevice.c --- vdr-1.5.8.org/dvbdevice.c 2008-01-23 17:07:56.980780574 -0500 +++ vdr-1.5.8/dvbdevice.c 2008-01-23 17:09:36.411979829 -0500 @@ -380,6 +380,7 @@ // The DVR device (will be opened and closed as needed): fd_dvr = -1; + shutdown = false; // The offset of the /dev/video devices: @@ -433,6 +434,8 @@ cDvbDevice::~cDvbDevice() { + shutdown = true; + CloseDvr(); delete spuDecoder; delete dvbTuner; delete ciAdapter; @@ -809,7 +812,7 @@ || pidHandlesVideo // for recording the PIDs must be shifted from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER ); - bool StartTransferMode = IsPrimaryDevice() && !DoTune + bool StartTransferMode = IsPrimaryDevice() && !DoTune && !Setup.LiveBuffer && (LiveView && HasPid(vpid ? vpid : apid) && (!pidHandlesVideo || (!pidHandlesAudio && (dpid ? pidHandles[ptAudio].pid != dpid : true)))// the PID is already set as DMX_PES_OTHER || !LiveView && (pidHandlesVideo || pidHandlesAudio) // a recording is going to shift the PIDs from DMX_PES_AUDIO/VIDEO to DMX_PES_OTHER ); @@ -1176,6 +1179,8 @@ bool cDvbDevice::OpenDvr(void) { + if (fd_dvr >= 0) + return true; CloseDvr(); fd_dvr = DvbOpen(DEV_DVB_DVR, CardIndex(), O_RDONLY | O_NONBLOCK, true); if (fd_dvr >= 0) @@ -1185,6 +1190,8 @@ void cDvbDevice::CloseDvr(void) { + if (!shutdown) + return; if (fd_dvr >= 0) { delete tsBuffer; tsBuffer = NULL; diff -Naur vdr-1.5.8.org/dvbdevice.h vdr-1.5.8/dvbdevice.h --- vdr-1.5.8.org/dvbdevice.h 2007-02-25 07:23:57.000000000 -0500 +++ vdr-1.5.8/dvbdevice.h 2008-01-23 17:09:36.417980263 -0500 @@ -36,6 +36,7 @@ ///< \return True if any devices are available. private: fe_type_t frontendType; + bool shutdown; int fd_osd, fd_audio, fd_video, fd_dvr, fd_stc, fd_ca; protected: virtual void MakePrimaryDevice(bool On); diff -Naur vdr-1.5.8.org/dvbplayer.c vdr-1.5.8/dvbplayer.c --- vdr-1.5.8.org/dvbplayer.c 2008-01-23 17:07:56.989781225 -0500 +++ vdr-1.5.8/dvbplayer.c 2008-01-23 17:19:09.047559724 -0500 @@ -187,12 +187,15 @@ class cDvbPlayer : public cPlayer, cThread { private: + bool isLiveRec; + bool rew; enum ePlayModes { pmPlay, pmPause, pmSlow, pmFast, pmStill }; enum ePlayDirs { pdForward, pdBackward }; static int Speeds[]; cNonBlockingFileReader *nonBlockingFileReader; cRingBufferFrame *ringBuffer; cBackTrace *backTrace; + char* filename; cFileName *fileName; cIndexFile *index; cUnbufferedFile *replayFile; @@ -214,7 +217,7 @@ virtual void Activate(bool On); virtual void Action(void); public: - cDvbPlayer(const char *FileName); + cDvbPlayer(const char *FileName,bool IsLiveRec = false); virtual ~cDvbPlayer(); bool Active(void) { return cThread::Running(); } void Pause(void); @@ -225,7 +228,9 @@ void SkipSeconds(int Seconds); void Goto(int Position, bool Still = false); virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + virtual int GetIndexOffset(void) { return index ? index->GetFirstFrame() : 0; } virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed); + void Rew(bool On); int GetFramesPerSec(void) { return framesPerSec; } }; @@ -235,9 +240,12 @@ #define SPEED_MULT 12 // the speed multiplier int cDvbPlayer::Speeds[] = { 0, -2, -4, -8, 1, 2, 4, 12, 0 }; -cDvbPlayer::cDvbPlayer(const char *FileName) +cDvbPlayer::cDvbPlayer(const char *FileName, bool IsLiveRec) :cThread("dvbplayer") { + DeviceClear(); + isLiveRec=IsLiveRec; + rew=false; nonBlockingFileReader = NULL; ringBuffer = NULL; backTrace = NULL; @@ -250,6 +258,7 @@ readIndex = writeIndex = -1; readFrame = NULL; playFrame = NULL; + filename = strdup(FileName); isyslog("replay %s", FileName); fileName = new cFileName(FileName, false); replayFile = fileName->Open(); @@ -258,7 +267,7 @@ framesPerSec = replayFile->GetFramesPerSec(); ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: - index = new cIndexFile(FileName, false); + index = new cIndexFile(FileName, false, isLiveRec); if (!index) esyslog("ERROR: can't allocate index"); else if (!index->Ok()) { @@ -270,8 +279,10 @@ cDvbPlayer::~cDvbPlayer() { + Cancel(3); Detach(); Save(); + free(filename); delete readFrame; // might not have been stored in the buffer in Action() delete index; delete fileName; @@ -329,6 +340,8 @@ { if (index) { int Index = index->GetResume(); + if (Index == -2) + Index = index->GetLast()-25; if (Index >= 0) { uchar FileNumber; int FileOffset; @@ -344,11 +357,13 @@ if (index) { int Index = writeIndex; if (Index >= 0) { - Index -= RESUMEBACKUP * GetFramesPerSec(); + Index -= isLiveRec ? 25 : RESUMEBACKUP * GetFramesPerSec(); if (Index > 0) Index = index->GetNextIFrame(Index, false); else Index = 0; + if (isLiveRec && !rew) + return index->StoreResume(-2); if (Index >= 0) return index->StoreResume(Index); } @@ -368,6 +383,7 @@ void cDvbPlayer::Action(void) { + int PollTimeouts = 0; uchar *b = NULL; uchar *p = NULL; int pc = 0; @@ -389,13 +405,43 @@ cCondWait::SleepMs(3); // this keeps the CPU load low Sleep = false; } + if (!index && isLiveRec) + { + index = new cIndexFile(filename, false, true); + if (!index->Ok()) + { + delete index; + index=NULL; + } + Sleep=true; + continue; + } + if (isLiveRec && index->GetLast() <= Setup.Frameswait) { + Sleep=true; + continue; + } cPoller Poller; if (DevicePoll(Poller, 100)) { LOCK_THREAD; + PollTimeouts=0; + // Read the next frame from the file: + if (isLiveRec && playDir==pdForward && index->GetLast()>100 && readIndex <= (index->GetFirstFrame()+25)) + { + if (playMode == pmStill || playMode == pmPause) + { + if (playMode == pmStill) + Empty(); + DevicePlay(); + playMode = pmPlay; + playDir = pdForward; + } + if (readIndex<=index->GetFirstFrame()+12) + Goto(index->GetFirstFrame()+25,false); + } if (playMode != pmStill && playMode != pmPause) { if (!readFrame && (replayFile || readIndex >= 0)) { if (!nonBlockingFileReader->Reading()) { @@ -436,11 +482,22 @@ int FileOffset; readIndex++; if (!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))) { + if (isLiveRec) + { + readIndex--; + goto PLAY; + } readIndex = -1; eof = true; continue; } + if (!Length) + { + readIndex+=2; + while(!(index->Get(readIndex, &FileNumber, &FileOffset, NULL, &Length) && NextFile(FileNumber, FileOffset))); + } } + else // allows replay even if the index file is missing Length = MAXFRAMESIZE; if (Length == -1) @@ -457,7 +514,7 @@ readFrame = new cFrame(b, -r, ftUnknown, readIndex); // hands over b to the ringBuffer b = NULL; } - else if (r == 0) + else if (r == 0 && !isLiveRec) eof = true; else if (r < 0 && errno == EAGAIN) WaitingForData = true; @@ -477,8 +534,7 @@ else Sleep = true; - // Get the next frame from the buffer: - +PLAY: // Get the next frame from the buffer: if (!playFrame) { playFrame = ringBuffer->Get(); p = NULL; @@ -521,6 +577,12 @@ else Sleep = true; } + else + if (++PollTimeouts == 8 && playDir == pdForward) + { + dsyslog("clearing device because of consecutive poll timeouts"); + Empty(); + } } cNonBlockingFileReader *nbfr = nonBlockingFileReader; @@ -759,10 +821,15 @@ return true; } +void cDvbPlayer::Rew(bool On) +{ + rew=On; +} + // --- cDvbPlayerControl ----------------------------------------------------- -cDvbPlayerControl::cDvbPlayerControl(const char *FileName) -:cControl(player = new cDvbPlayer(FileName)) +cDvbPlayerControl::cDvbPlayerControl(const char *FileName, bool IsLiveRec) +:cControl(player = new cDvbPlayer(FileName,IsLiveRec)) { } @@ -819,10 +886,16 @@ return -1; } -bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame) +bool cDvbPlayerControl::GetIndex(int &Current, int &Total, bool SnapToIFrame, bool onlyExisting) { if (player) { player->GetIndex(Current, Total, SnapToIFrame); + if (onlyExisting) + { + int i=player->GetIndexOffset(); + Total-=i; + Current-=i; + } return true; } return false; @@ -845,3 +918,8 @@ return player->GetFramesPerSec(); return FRAMESPERSEC; } + +void cDvbPlayerControl::Rew(bool On) +{ + player->Rew(On); +} diff -Naur vdr-1.5.8.org/dvbplayer.h vdr-1.5.8/dvbplayer.h --- vdr-1.5.8.org/dvbplayer.h 2008-01-23 17:07:56.997781805 -0500 +++ vdr-1.5.8/dvbplayer.h 2008-01-23 17:20:06.219709451 -0500 @@ -19,7 +19,7 @@ private: cDvbPlayer *player; public: - cDvbPlayerControl(const char *FileName); + cDvbPlayerControl(const char *FileName, bool IsLiveRec = false); // Sets up a player for the given file. virtual ~cDvbPlayerControl(); bool Active(void); @@ -42,7 +42,7 @@ // The sign of 'Seconds' determines the direction in which to skip. // Use a very large negative value to go all the way back to the // beginning of the recording. - bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false); + bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false, bool onlyExisting = false); // Returns the current and total frame index, optionally snapped to the // nearest I-frame. bool GetReplayMode(bool &Play, bool &Forward, int &Speed); @@ -54,6 +54,9 @@ void Goto(int Index, bool Still = false); // Positions to the given index and displays that frame as a still picture // if Still is true. + virtual bool IsLiveRecording(bool liveview=false) {return false;} + virtual void LiveReplay() {}; + void Rew(bool On = true); int GetFramesPerSec(); // Returns the number of frames per second for the current recording. }; diff -Naur vdr-1.5.8.org/keys.c vdr-1.5.8/keys.c --- vdr-1.5.8.org/keys.c 2007-08-11 07:30:18.000000000 -0400 +++ vdr-1.5.8/keys.c 2008-01-23 17:09:36.420980481 -0500 @@ -39,6 +39,8 @@ { kRecord, trNOOP("Key$Record") }, { kFastFwd, trNOOP("Key$FastFwd") }, { kFastRew, trNOOP("Key$FastRew") }, + { kJumpFwd, trNOOP("Key$JumpFwd") }, + { kJumpRew, trNOOP("Key$JumpRew") }, { kNext, trNOOP("Key$Next") }, { kPrev, trNOOP("Key$Prev") }, { kPower, trNOOP("Key$Power") }, diff -Naur vdr-1.5.8.org/keys.h vdr-1.5.8/keys.h --- vdr-1.5.8.org/keys.h 2007-08-04 10:40:23.000000000 -0400 +++ vdr-1.5.8/keys.h 2008-01-23 17:09:36.421980553 -0500 @@ -33,6 +33,8 @@ kRecord, kFastFwd, kFastRew, + kJumpFwd, + kJumpRew, kNext, kPrev, kPower, diff -Naur vdr-1.5.8.org/menu.c vdr-1.5.8/menu.c --- vdr-1.5.8.org/menu.c 2008-01-23 17:07:57.050785643 -0500 +++ vdr-1.5.8/menu.c 2008-01-23 17:25:30.052203226 -0500 @@ -663,6 +663,7 @@ cMenuEditTimer::cMenuEditTimer(cTimer *Timer, bool New) :cOsdMenu(tr("Edit timer"), 12) { + withBuffer=true; firstday = NULL; timer = Timer; addIfConfirmed = New; @@ -671,8 +672,24 @@ if (New) data.SetFlags(tfActive); channel = data.Channel()->Number(); + Setup(); + SetFirstDayItem(); + } + Timers.IncBeingEdited(); +} + +void cMenuEditTimer::Setup() +{ + int current = Current(); + Clear(); Add(new cMenuEditBitItem( tr("Active"), &data.flags, tfActive)); Add(new cMenuEditChanItem(tr("Channel"), &channel)); + if (cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel)) && !timer->Recording()) + { + Add(new cMenuEditBoolItem(tr("LiveBuffer"), &withBuffer)); + } + else + withBuffer=true; Add(new cMenuEditDateItem(tr("Day"), &data.day, &data.weekdays)); Add(new cMenuEditTimeItem(tr("Start"), &data.start)); Add(new cMenuEditTimeItem(tr("Stop"), &data.stop)); @@ -680,9 +697,8 @@ Add(new cMenuEditIntItem( tr("Priority"), &data.priority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Lifetime"), &data.lifetime, 0, MAXLIFETIME)); Add(new cMenuEditStrItem( tr("File"), data.file, sizeof(data.file), tr(FileNameChars))); - SetFirstDayItem(); - } - Timers.IncBeingEdited(); + SetCurrent(Get(current)); + Display(); } cMenuEditTimer::~cMenuEditTimer() @@ -707,8 +723,10 @@ eOSState cMenuEditTimer::ProcessKey(eKeys Key) { + int oldchannel = channel; eOSState state = cOsdMenu::ProcessKey(Key); - + if (oldchannel!=channel) + Setup(); if (state == osUnknown) { switch (Key) { case kOk: { @@ -730,6 +748,52 @@ timer->Matches(); Timers.SetModified(); isyslog("timer %s %s (%s)", *timer->ToDescr(), addIfConfirmed ? "added" : "modified", timer->HasFlags(tfActive) ? "active" : "inactive"); + if (cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel)) && !timer->Recording() && withBuffer) + { + if (data.StartTime() < time(NULL)) + { + cIndexFile *index = new cIndexFile(cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel))->GetFileName(),false); + int now=index->GetLast() - 1; + cMarks marks; + marks.Load(cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel))->GetFileName()); + while (cMark *m = marks.First()) + marks.Del(m); + marks.Add(now-(time(NULL)-data.StartTime())*FRAMESPERSEC > index->GetFirstFrame() ? now-(time(NULL)-data.StartTime())*FRAMESPERSEC : index->GetFirstFrame()+12 ); + marks.Save(); + if (data.StopTime() < time(NULL)) + { + if (now-(time(NULL)-data.StopTime())*FRAMESPERSEC > index->GetFirstFrame()) + { + marks.Add(now-(time(NULL)-data.StopTime())*FRAMESPERSEC); + marks.Save(); + if (!cCutter::Active()) { + if (!marks.Count()) + Skins.Message(mtError, tr("No editing marks defined!")); + else if (!cCutter::Start(cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel))->GetFileName(),cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel)), false,timer)) + Skins.Message(mtError, tr("Can't start editing process!")); + else + Skins.Message(mtInfo, tr("Editing process started")); + } + else + Skins.Message(mtError, tr("Editing process already active!")); + } + } + else + { + if (!cCutter::Active()) { + if (!marks.Count()) + Skins.Message(mtError, tr("No editing marks defined!")); + else if (!cCutter::Start(cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel))->GetFileName(),cLiveRecorderControl::GetLiveRecorder(Channels.GetByNumber(channel)),true,timer)) + Skins.Message(mtError, tr("Can't start editing process!")); + else + Skins.Message(mtInfo, tr("Editing process started")); + } + else + Skins.Message(mtError, tr("Editing process already active!")); + } + delete index; + } + } addIfConfirmed = false; } } @@ -1021,7 +1085,7 @@ const cEvent *event; const cChannel *channel; bool withDate; - int timerMatch; + int timerMatch, liveBufferMatch; cMenuScheduleItem(const cEvent *Event, cChannel *Channel = NULL, bool WithDate = false); static void SetSortMode(eScheduleSortMode SortMode) { sortMode = SortMode; } static void IncSortMode(void) { sortMode = eScheduleSortMode((sortMode == ssmAllAll) ? ssmAllThis : sortMode + 1); } @@ -1053,24 +1117,28 @@ } static char *TimerMatchChars = " tT"; +static char *LiveBufferMatchChars = " lL"; bool cMenuScheduleItem::Update(bool Force) { bool result = false; int OldTimerMatch = timerMatch; + int OldLiveBufferMatch = liveBufferMatch; Timers.GetMatch(event, &timerMatch); - if (Force || timerMatch != OldTimerMatch) { + liveBufferMatch = cLiveRecorderControl::MatchesEvent(event); + if (Force || timerMatch != OldTimerMatch || liveBufferMatch != OldLiveBufferMatch) { char *buffer = NULL; char t = TimerMatchChars[timerMatch]; char v = event->Vps() && (event->Vps() - event->StartTime()) ? 'V' : ' '; char r = event->SeenWithin(30) && event->IsRunning() ? '*' : ' '; + char l = LiveBufferMatchChars[liveBufferMatch]; const char *csn = channel ? channel->ShortName(true) : NULL; if (channel && withDate) - asprintf(&buffer, "%d\t%.*s\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, 6, *event->GetDateString(), *event->GetTimeString(), t, v, r, event->Title()); + asprintf(&buffer, "%d\t%.*s\t%.*s\t%s\t%c%c%c%c\t%s", channel->Number(), 6, channel->ShortName(true), 6, *event->GetDateString(), *event->GetTimeString(), l, t, v, r, event->Title()); else if (channel) - asprintf(&buffer, "%d\t%.*s\t%s\t%c%c%c\t%s", channel->Number(), Utf8SymChars(csn, 6), csn, *event->GetTimeString(), t, v, r, event->Title()); + asprintf(&buffer, "%d\t%.*s\t%s\t%c%c%c%c\t%s", channel->Number(), 6, channel->ShortName(true), *event->GetTimeString(), l, t, v, r, event->Title()); else - asprintf(&buffer, "%.*s\t%s\t%c%c%c\t%s", 6, *event->GetDateString(), *event->GetTimeString(), t, v, r, event->Title()); + asprintf(&buffer, "%.*s\t%s\t%c%c%c%c\t%s", 6, *event->GetDateString(), *event->GetTimeString(), l, t, v, r, event->Title()); SetText(buffer, false); result = true; } @@ -1188,6 +1256,8 @@ timer = t; return AddSubMenu(new cMenuEditTimer(timer)); } + else if (cLiveRecorderControl::InLiveBuffer(timer)) + return AddSubMenu(new cMenuEditTimer(timer,true)); else { Timers.Add(timer); Timers.SetModified(); @@ -1437,6 +1507,8 @@ timer = t; return AddSubMenu(new cMenuEditTimer(timer)); } + else if (cLiveRecorderControl::InLiveBuffer(timer)) + return AddSubMenu(new cMenuEditTimer(timer,true)); else { Timers.Add(timer); Timers.SetModified(); @@ -2687,6 +2759,7 @@ Add(new cMenuEditBoolItem(tr("Setup.Recording$Split edited files"), &data.SplitEditedFiles)); } + // --- cMenuSetupReplay ------------------------------------------------------ class cMenuSetupReplay : public cMenuSetupBase { @@ -2730,6 +2803,54 @@ Add(new cMenuEditIntItem( tr("Setup.Miscellaneous$Initial volume"), &data.InitialVolume, -1, 255, tr("Setup.Miscellaneous$as before"))); } +// --- cMenuSetupLiveBuffer--------------------------------------------------- + +class cMenuSetupLiveBuffer : public cMenuSetupBase { +private: + void Setup(); +public: + eOSState ProcessKey(eKeys Key); + cMenuSetupLiveBuffer(void); + }; + +cMenuSetupLiveBuffer::cMenuSetupLiveBuffer(void) +{ + SetSection("LiveBuffer"); + Setup(); +} + +void cMenuSetupLiveBuffer::Setup(void) +{ + int current=Current(); + Clear(); + Add(new cMenuEditBoolItem(tr("LiveBuffer"), &data.LiveBuffer)); + if (data.LiveBuffer) { + Add(new cMenuEditIntItem(tr("Setup.LiveBuffer$LiveBufferSize"), &data.LiveBufferSize, 1, 1000000)); + Add(new cMenuEditBoolItem(tr("Setup.LiveBuffer$Go automatically into Replaymode"),&data.LiveReplay)); + Add(new cMenuEditBoolItem(tr("Setup.LiveBuffer$Keep paused LiveBuffer"), &data.KeepBuffer)); + Add(new cMenuEditBoolItem(tr("Setup.LiveBuffer$Keep last LiveBuffer"), &data.KeepLastBuffer)); + if (data.KeepLastBuffer) { + Add(new cMenuEditIntItem(tr("Setup.LiveBuffer$Last LiveBuffers"), &data.NumLastBuffers, 1, 100)); + Add(new cMenuEditIntItem(tr("Setup.LiveBuffer$Zap Timeout (s)"), &data.BufferTimeOut)); + } + Add(new cMenuEditIntItem(tr("Setup.Miscellaneous$Min. user inactivity (min)"),&data.MinLiveBufferInactivity)); + Add(new cMenuEditIntItem("Frameswait", &data.Frameswait, 0, 100)); + } + SetCurrent(Get(current)); + Display(); +} + +eOSState cMenuSetupLiveBuffer::ProcessKey(eKeys Key) +{ + int oldLiveBuffer = data.LiveBuffer; + int oldKeepLastBuffer = data.KeepLastBuffer; + eOSState state = cMenuSetupBase::ProcessKey(Key); + + if (Key != kNone && (data.LiveBuffer != oldLiveBuffer || data.KeepLastBuffer != oldKeepLastBuffer)) + Setup(); + return state; +} + // --- cMenuSetupPluginItem -------------------------------------------------- class cMenuSetupPluginItem : public cOsdItem { @@ -2828,6 +2949,7 @@ Add(new cOsdItem(hk(tr("Recording")), osUser6)); Add(new cOsdItem(hk(tr("Replay")), osUser7)); Add(new cOsdItem(hk(tr("Miscellaneous")), osUser8)); + Add(new cOsdItem(hk(tr("LiveBuffer")), osLiveBuffer)); if (cPluginManager::HasPlugins()) Add(new cOsdItem(hk(tr("Plugins")), osUser9)); Add(new cOsdItem(hk(tr("Restart")), osUser10)); @@ -2856,6 +2978,7 @@ case osUser6: return AddSubMenu(new cMenuSetupRecord); case osUser7: return AddSubMenu(new cMenuSetupReplay); case osUser8: return AddSubMenu(new cMenuSetupMisc); + case osLiveBuffer: return AddSubMenu(new cMenuSetupLiveBuffer); case osUser9: return AddSubMenu(new cMenuSetupPlugins); case osUser10: return Restart(); default: ; @@ -2901,6 +3024,7 @@ cancelEditingItem = NULL; stopRecordingItem = NULL; recordControlsState = 0; + isLiveRec = cControl::Control() ? cControl::Control()->IsLiveRecording() : false; Set(); // Initial submenus: @@ -2985,7 +3109,7 @@ if (Force || NewReplaying != replaying) { replaying = NewReplaying; // Replay control: - if (replaying && !stopReplayItem) + if (replaying && !stopReplayItem && !isLiveRec) // TRANSLATORS: note the leading blank! Add(stopReplayItem = new cOsdItem(tr(" Stop replaying"), osStopReplay)); else if (stopReplayItem && !replaying) { @@ -2993,7 +3117,7 @@ stopReplayItem = NULL; } // Color buttons: - SetHelp(!replaying ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL); + SetHelp(!replaying || isLiveRec ? tr("Button$Record") : NULL, tr("Button$Audio"), replaying ? NULL : tr("Button$Pause"), replaying ? cControl::Control() && cControl::Control()->IsLiveRecording(true) ? tr("Replaymode") : tr("Button$Stop") : cReplayControl::LastReplayed() ? tr("Button$Resume") : NULL); result = true; } @@ -3082,7 +3206,7 @@ default: switch (Key) { case kRecord: case kRed: if (!HadSubMenu) - state = replaying ? osContinue : osRecord; + state = replaying && !isLiveRec ? osContinue : osRecord; break; case kGreen: if (!HadSubMenu) { cRemote::Put(kAudio, true); @@ -3092,8 +3216,15 @@ case kYellow: if (!HadSubMenu) state = replaying ? osContinue : osPause; break; - case kBlue: if (!HadSubMenu) + case kBlue: if (!HadSubMenu) { + if (cControl::Control() && cControl::Control()->IsLiveRecording(true)) + { + cControl::Control()->LiveReplay(); + state = osEnd; + } + else state = replaying ? osStopReplay : cReplayControl::LastReplayed() ? osReplay : osContinue; + } break; default: break; } @@ -3159,6 +3290,8 @@ cDisplayChannel::cDisplayChannel(int Number, bool Switched) :cOsdObject(true) { + if (cControl::Control()) + cControl::Control()->Hide(); currentDisplayChannel = this; group = -1; withInfo = !Switched || Setup.ShowInfoOnChSwitch; @@ -3178,6 +3311,8 @@ cDisplayChannel::cDisplayChannel(eKeys FirstKey) :cOsdObject(true) { + if (cControl::Control()) + cControl::Control()->Hide(); currentDisplayChannel = this; group = -1; number = 0; @@ -3602,7 +3737,7 @@ // --- cRecordControl -------------------------------------------------------- -cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause) +cRecordControl::cRecordControl(cDevice *Device, cTimer *Timer, bool Pause, bool IsBeginning, bool IsBufferRec) { // We're going to manipulate an event here, so we need to prevent // others from modifying any EPG data: @@ -3631,6 +3766,9 @@ cRecording Recording(timer, event); fileName = strdup(Recording.FileName()); + if (IsBufferRec) + return; + // crude attempt to avoid duplicate recordings: if (cRecordControls::GetRecordControl(fileName)) { isyslog("already recording: '%s'", fileName); @@ -3653,7 +3791,7 @@ isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); - recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids()); + recorder = new cRecorder(fileName, ch->GetChannelID(), timer->Priority(), ch->Vpid(), ch->Apids(), ch->Dpids(), ch->Spids(), false, IsBeginning); if (device->AttachReceiver(recorder)) { Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); @@ -3733,7 +3871,7 @@ cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; -bool cRecordControls::Start(cTimer *Timer, bool Pause) +bool cRecordControls::Start(cTimer *Timer, bool Pause, const char **fileName) { static time_t LastNoDiskSpaceMessage = 0; int FreeMB = 0; @@ -3753,6 +3891,52 @@ LastNoDiskSpaceMessage = 0; ChangeState(); + if (!Pause && !Timer && !fileName && cLiveRecorderControl::GetLiveRecorder()) + { + Timer = new cTimer(true, Pause); + Timers.Add(Timer); + Timers.SetModified(); + int Current,Total; + cLiveRecorderControl::GetLiveRecorder()->GetReplayControl()->GetIndex(Current,Total,true); + if (Total-Current > 250) + { + cMarks marks; + marks.Load(cLiveRecorderControl::GetLiveRecorder()->GetFileName()); + while (cMark *m = marks.First()) + marks.Del(m); + marks.Add(Current); + marks.Save(); + if (Timer->StopTime() < time(NULL)) + { + marks.Add(Total-(time(NULL)-Timer->StopTime())*FRAMESPERSEC); + marks.Save(); + if (!cCutter::Active()) { + if (!marks.Count()) + Skins.Message(mtError, tr("No editing marks defined!")); + else if (!cCutter::Start(cLiveRecorderControl::GetLiveRecorder()->GetFileName(),cLiveRecorderControl::GetLiveRecorder(), false,Timer)) + Skins.Message(mtError, tr("Can't start editing process!")); + else + Skins.Message(mtInfo, tr("Editing process started")); + } + else + Skins.Message(mtError, tr("Editing process already active!")); + } + else +{ + if (!cCutter::Active()) { + if (!marks.Count()) + Skins.Message(mtError, tr("No editing marks defined!")); + else if (!cCutter::Start(cLiveRecorderControl::GetLiveRecorder()->GetFileName(),cLiveRecorderControl::GetLiveRecorder(),true,Timer)) + Skins.Message(mtError, tr("Can't start editing process!")); + else + Skins.Message(mtInfo, tr("Editing process started")); + } + else + Skins.Message(mtError, tr("Editing process already active!")); + } + return true; + } + } int ch = Timer ? Timer->Channel()->Number() : cDevice::CurrentChannel(); cChannel *channel = Channels.GetByNumber(ch); @@ -3768,7 +3952,9 @@ if (!Timer || Timer->Matches()) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { if (!RecordControls[i]) { - RecordControls[i] = new cRecordControl(device, Timer, Pause); + RecordControls[i] = new cRecordControl(device, Timer, Pause, fileName!=NULL); + if (fileName) + *fileName = RecordControls[i]->FileName(); return RecordControls[i]->Process(time(NULL)); } } @@ -3901,12 +4087,19 @@ cReplayControl *cReplayControl::currentReplayControl = NULL; char *cReplayControl::fileName = NULL; +char *cReplayControl::fileName_l = NULL; char *cReplayControl::title = NULL; -cReplayControl::cReplayControl(void) -:cDvbPlayerControl(fileName) +cReplayControl::cReplayControl(bool IsLiveRec,cLiveRecorder* Recorder) +:cDvbPlayerControl(fileName, IsLiveRec) { currentReplayControl = this; + if (!IsLiveRec) + cTransferControl::receiverDevice=NULL; + isLiveRec=IsLiveRec; + livereplay=false; + recorder=Recorder; + lastViewed=0; displayReplay = NULL; visible = modeOnly = shown = displayFrames = false; lastCurrent = lastTotal = -1; @@ -3917,7 +4110,7 @@ marks.Load(fileName); cRecording Recording(fileName); cStatus::MsgReplaying(this, Recording.Name(), Recording.FileName(), true); - SetTrackDescriptions(false); + SetTrackDescriptions(IsLiveRec ? cDevice::CurrentChannel() : false); } cReplayControl::~cReplayControl() @@ -3929,11 +4122,15 @@ currentReplayControl = NULL; } -void cReplayControl::SetRecording(const char *FileName, const char *Title) +void cReplayControl::SetRecording(const char *FileName, const char *Title, bool IsLiveRec) { free(fileName); free(title); fileName = FileName ? strdup(FileName) : NULL; + if (!IsLiveRec) { + free(fileName_l); + fileName_l = FileName ? strdup(FileName) : NULL; + } title = Title ? strdup(Title) : NULL; } @@ -3944,14 +4141,14 @@ const char *cReplayControl::LastReplayed(void) { - return fileName; + return fileName_l; } void cReplayControl::ClearLastReplayed(const char *FileName) { - if (fileName && FileName && strcmp(fileName, FileName) == 0) { - free(fileName); - fileName = NULL; + if (fileName_l && FileName && strcmp(fileName_l, FileName) == 0) { + free(fileName_l); + fileName_l = NULL; } } @@ -3972,6 +4169,7 @@ void cReplayControl::Hide(void) { + mutex.Lock(); if (visible) { delete displayReplay; displayReplay = NULL; @@ -3982,6 +4180,7 @@ lastSpeed = -2; // an invalid value timeSearchActive = false; } + mutex.Unlock(); } void cReplayControl::ShowMode(void) @@ -4013,10 +4212,28 @@ { int Current, Total; - if (GetIndex(Current, Total) && Total > 0) { + if (GetIndex(Current, Total, false, isLiveRec) && Total > 0) { if (!visible) { + if (isLiveRec) + { + int current,total; + GetIndex(current, total, false); + int offs = total - Total; + for (cMark *m = marks.First(); m;) { + if (m->position >= offs) { + marks_offs.Add(m->position-offs); + m = marks.Next(m); + } + else { + cMark *mt = m; + m = marks.Next(m); + marks.Del(mt); + marks.Save(); + } + } + } displayReplay = Skins.Current()->DisplayReplay(modeOnly); - displayReplay->SetMarks(&marks); + displayReplay->SetMarks(isLiveRec ? &marks_offs : &marks); SetNeedsFastResponse(true); visible = true; } @@ -4026,6 +4243,27 @@ lastCurrent = lastTotal = -1; } if (Total != lastTotal) { + if (isLiveRec) + { + int current,total; + GetIndex(current, total, false); + int offs = total - Total; + while (cMark *m = marks_offs.First()) + marks_offs.Del(m); + for (cMark *m = marks.First(); m;) { + if (m->position >= offs) { + marks_offs.Add(m->position-offs); + m = marks.Next(m); + } + else { + cMark *mt = m; + m = marks.Next(m); + marks.Del(mt); + marks.Save(); + } + + } + } displayReplay->SetTotal(IndexToHMSF(Total, false, GetFramesPerSec())); if (!Initial) displayReplay->Flush(); @@ -4082,6 +4320,8 @@ case kLeft: case kFastFwd: case kRight: { + if (isLiveRec) + rew(); int dir = ((Key == kRight || Key == kFastFwd) ? 1 : -1); if (dir > 0) Seconds = min(Total - Current - STAY_SECONDS_OFF_END, Seconds); @@ -4094,8 +4334,12 @@ case kPause: case kDown: case kOk: + if (isLiveRec) + rew(); Seconds = min(Total - STAY_SECONDS_OFF_END, Seconds); - Goto(Seconds * GetFramesPerSec(), Key == kDown || Key == kPause || Key == kOk); + int current, total; + GetIndex(current, total, false); + Goto(Seconds * GetFramesPerSec()+total-lastTotal, Key == kDown || Key == kPause || Key == kOk); timeSearchActive = false; break; default: @@ -4190,11 +4434,43 @@ void cReplayControl::EditCut(void) { if (fileName) { + cTimer *timer=NULL; + if (isLiveRec) + { + int Current, current, Total, total; + GetIndex(Current, Total, false, true); + GetIndex(current, total, false); + int offs = total - Total; + for (cMark *m = marks.First(); m;) + if (m->position < offs) { + cMark* mt = m; + m = marks.Next(m); + marks.Del(mt); + } + else + m = marks.Next(m); + marks.Save(); + } + if (isLiveRec && marks.Count()) + { + time_t t = time(NULL); + struct tm tm_r; + struct tm *now = localtime_r(&t, &tm_r); + timer = new cTimer; + cIndexFile index(cLiveRecorderControl::GetLiveRecorder()->GetFileName(),false); + int last = index.GetLast(); + int temp = now->tm_min - (last-marks.First()->position)/FRAMESPERSEC/60; + timer->start = (now->tm_hour + temp/60)*100 + temp%60; + temp = now->tm_min - (last-marks.Last()->position)/FRAMESPERSEC/60; + timer->stop = (now->tm_hour + temp/60)*100 + now->tm_min + temp%60; + timer->day = timer->SetTime(t-(last-marks.First()->position)/FRAMESPERSEC,0); + snprintf(timer->file, sizeof(timer->file), "%s", *Setup.NameInstantRecord ? Setup.NameInstantRecord : timer->channel->Name()); + } Hide(); if (!cCutter::Active()) { if (!marks.Count()) Skins.Message(mtError, tr("No editing marks defined!")); - else if (!cCutter::Start(fileName)) + else if (!cCutter::Start(fileName,isLiveRec ? cLiveRecorderControl::GetLiveRecorder() : NULL,false,timer)) Skins.Message(mtError, tr("Can't start editing process!")); else Skins.Message(mtInfo, tr("Editing process started")); @@ -4223,6 +4499,17 @@ } } +void cReplayControl::rew(void) +{ + if (!lastViewed) + { + int Current, Total; + GetIndex(Current, Total); + lastViewed=Current; + } + recorder->rew(); +} + cOsdObject *cReplayControl::GetInfo(void) { cRecording *Recording = Recordings.GetByName(cReplayControl::LastReplayed()); @@ -4233,7 +4520,7 @@ eOSState cReplayControl::ProcessKey(eKeys Key) { - if (!Active()) + if (!Active() && !isLiveRec) return osEnd; if (visible) { if (timeoutShow && time(NULL) > timeoutShow) { @@ -4255,31 +4542,86 @@ bool DoShowMode = true; switch (Key) { // Positioning: - case kPlay: - case kUp: Play(); break; - case kPause: - case kDown: Pause(); break; + case kPlay: if (isLiveRec && Setup.LiveReplay) + livereplay=true; + bool play,forward; + int speed; + GetReplayMode(play,forward,speed); + if (play && forward && speed==-1) + { + if (visible && !modeOnly) { + Hide(); + DoShowMode = true; + } + else + Show(); + } + else + Play(); + break; + case kUp: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + Play(); break; + case kPause: if (isLiveRec && Setup.LiveReplay) + livereplay=true; + if (isLiveRec) + rew(); + Pause(); break; + case kDown: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + if (isLiveRec) + rew(); + Pause(); break; case kFastRew|k_Release: case kLeft|k_Release: if (Setup.MultiSpeedMode) break; - case kFastRew: - case kLeft: Backward(); break; + case kFastRew: if (isLiveRec && Setup.LiveReplay) + livereplay=true; + if (isLiveRec) + rew(); + Backward(); break; + case kLeft: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + if (isLiveRec) + rew(); + Backward(); break; case kFastFwd|k_Release: case kRight|k_Release: if (Setup.MultiSpeedMode) break; - case kFastFwd: - case kRight: Forward(); break; - case kRed: TimeSearch(); break; + case kFastFwd: if (isLiveRec && Setup.LiveReplay) + livereplay=true; + Forward(); break; + case kRight: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + Forward(); break; + case kRed: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + TimeSearch(); break; case kGreen|k_Repeat: - case kGreen: SkipSeconds(-60); break; + case kGreen: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + case kJumpRew: if (isLiveRec) + rew(); + SkipSeconds(-60); break; case kYellow|k_Repeat: - case kYellow: SkipSeconds( 60); break; - case kStop: - case kBlue: Hide(); + case kYellow: if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + case kJumpFwd: SkipSeconds( 60); break; + case kStop: if (isLiveRec) + cLiveRecorderControl::GetLiveRecorder()->rew(false); + if (isLiveRec && !livereplay) + return osStopReplay; + case kBlue: if (isLiveRec) + cLiveRecorderControl::GetLiveRecorder()->rew(false); + if (isLiveRec && !livereplay && (!visible || modeOnly)) + return osBUnknown; + Hide(); Stop(); return osEnd; default: { DoShowMode = false; + if (isLiveRec && !livereplay && (!visible || modeOnly || Key==kOk) && Key!=kBack) + return osBUnknown; switch (Key) { // Editing: case kMarkToggle: MarkToggle(); break; @@ -4308,7 +4650,9 @@ else Show(); break; - case kBack: return osRecordings; + case kBack: if (isLiveRec) + {if (lastViewed) Goto(lastViewed); break;}//return osBUnknown; + return osRecordings; default: return osUnknown; } } diff -Naur vdr-1.5.8.org/menu.h vdr-1.5.8/menu.h --- vdr-1.5.8.org/menu.h 2007-08-12 06:35:42.000000000 -0400 +++ vdr-1.5.8/menu.h 2008-01-23 17:09:36.426980915 -0500 @@ -18,6 +18,7 @@ #include "menuitems.h" #include "recorder.h" #include "skins.h" +#include "transfer.h" class cMenuText : public cOsdMenu { private: @@ -33,12 +34,14 @@ class cMenuEditTimer : public cOsdMenu { private: + int withBuffer; cTimer *timer; cTimer data; int channel; bool addIfConfirmed; cMenuEditDateItem *firstday; void SetFirstDayItem(void); + void Setup(void); public: cMenuEditTimer(cTimer *Timer, bool New = false); virtual ~cMenuEditTimer(); @@ -59,6 +62,7 @@ time_t lastDiskSpaceCheck; int lastFreeMB; bool replaying; + bool isLiveRec; cOsdItem *stopReplayItem; cOsdItem *cancelEditingItem; cOsdItem *stopRecordingItem; @@ -164,7 +168,7 @@ char *fileName; bool GetEvent(void); public: - cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false); + cRecordControl(cDevice *Device, cTimer *Timer = NULL, bool Pause = false, bool IsBeginning = false, bool IsBufferRec = false); virtual ~cRecordControl(); bool Process(time_t t); cDevice *Device(void) { return device; } @@ -172,6 +176,7 @@ const char *InstantId(void) { return instantId; } const char *FileName(void) { return fileName; } cTimer *Timer(void) { return timer; } + cFileWriter *GetWriter(void) {return recorder->GetWriter();} }; class cRecordControls { @@ -179,7 +184,7 @@ static cRecordControl *RecordControls[]; static int state; public: - static bool Start(cTimer *Timer = NULL, bool Pause = false); + static bool Start(cTimer *Timer = NULL, bool Pause = false, const char **fileName = NULL); static void Stop(const char *InstantId); static bool PauseLiveVideo(void); static const char *GetInstantId(const char *LastInstantId); @@ -194,8 +199,13 @@ class cReplayControl : public cDvbPlayerControl { private: + bool isLiveRec; + bool livereplay; + cLiveRecorder* recorder; + cMutex mutex; + int lastViewed; cSkinDisplayReplay *displayReplay; - cMarks marks; + cMarks marks,marks_offs; bool visible, modeOnly, shown, displayFrames; int lastCurrent, lastTotal; bool lastPlay, lastForward; @@ -208,7 +218,7 @@ void TimeSearch(void); void ShowTimed(int Seconds = 0); static cReplayControl *currentReplayControl; - static char *fileName; + static char *fileName, *fileName_l; static char *title; void ShowMode(void); bool ShowProgress(bool Initial); @@ -216,16 +226,20 @@ void MarkJump(bool Forward); void MarkMove(bool Forward); void EditCut(void); + void AddBeginning(void); void EditTest(void); + void rew(void); public: - cReplayControl(void); + cReplayControl(bool IsLiveRec=false, cLiveRecorder* Recorder=NULL); virtual ~cReplayControl(); virtual cOsdObject *GetInfo(void); virtual eOSState ProcessKey(eKeys Key); virtual void Show(void); virtual void Hide(void); + virtual bool IsLiveRecording(bool liveview=false) {return isLiveRec && (!liveview || !livereplay);} + virtual void LiveReplay() {livereplay=true;} bool Visible(void) { return visible; } - static void SetRecording(const char *FileName, const char *Title); + static void SetRecording(const char *FileName, const char *Title, bool IsLiveRec = false); static const char *NowReplaying(void); static const char *LastReplayed(void); static void ClearLastReplayed(const char *FileName); diff -Naur vdr-1.5.8.org/osdbase.h vdr-1.5.8/osdbase.h --- vdr-1.5.8.org/osdbase.h 2007-06-09 07:49:00.000000000 -0400 +++ vdr-1.5.8/osdbase.h 2008-01-23 17:09:36.427980987 -0500 @@ -44,6 +44,8 @@ osUser8, osUser9, osUser10, + osBUnknown, + osLiveBuffer }; class cOsdItem : public cListObject { diff -Naur vdr-1.5.8.org/player.h vdr-1.5.8/player.h --- vdr-1.5.8.org/player.h 2006-01-06 06:29:27.000000000 -0500 +++ vdr-1.5.8/player.h 2008-01-23 17:09:36.427980987 -0500 @@ -47,6 +47,7 @@ virtual bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return false; } // Returns the current and total frame index, optionally snapped to the // nearest I-frame. + virtual int GetIndexOffset(void) { return 0; } virtual bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return false; } // Returns the current replay mode (if applicable). // 'Play' tells whether we are playing or pausing, 'Forward' tells whether @@ -72,6 +73,9 @@ virtual ~cControl(); virtual void Hide(void) = 0; virtual cOsdObject *GetInfo(void); + virtual bool IsLiveRecording(bool liveview=false) { return false;} + virtual void LiveReplay() {} + bool Attached(void) { return attached; } bool GetIndex(int &Current, int &Total, bool SnapToIFrame = false) { return player->GetIndex(Current, Total, SnapToIFrame); } bool GetReplayMode(bool &Play, bool &Forward, int &Speed) { return player->GetReplayMode(Play, Forward, Speed); } static void Launch(cControl *Control); diff -Naur vdr-1.5.8.org/README.LiveBuffer vdr-1.5.8/README.LiveBuffer --- vdr-1.5.8.org/README.LiveBuffer 1969-12-31 19:00:00.000000000 -0500 +++ vdr-1.5.8/README.LiveBuffer 2008-01-23 17:09:36.394978598 -0500 @@ -0,0 +1,66 @@ +LiveBuffer-Patch +---------------- + +Written by: Thomas Bergwinkl + +Homepage: http://home.vrweb.de/~bergwinkl.thomas + +Description: + +With this patch vdr always records the current channel. So you are able to +rewind and watch a interesting scene again. Or you have seen a movie from the +beginning but didn't record it. With the LiveBuffer you can record it +completely, if it is in the livebuffer. + +You can enable/disable the livebuffer in the recording menu: + +LiveBuffer: Turns on/off the livebuffer +LiveBufferSize: The max. size (in MB) of one livebuffer +Go automatically into Replaymode: If you press a key like play,pause, ..., + you come automatically into the replaymode +Keep paused LiveBuffer: If activated, a LiveBuffer, which is paused, + will be continued if you switch channel +Keep last LiveBuffer: The last livebuffer will always be kept, + if possible +Last LiveBuffers: Number of last livebuffers, which should be + kept. +Zap Timeout (s): The time until a livebuffer recording counts + as "last" for option 'Keep last LiveBuffer' +Min. user inactivity (min): After this time the livebuffer shutsdown. + '0' means that there will be no shutdown. +Frameswait: The number of frames, which have to be + received, until the replay of the livebuffer + starts + +How to control the livebuffer: + +In normal liveview you can pause, rewind, ... with the according key. The +play key displays the progress bar of the livebuffer. + +If you have no such keys like play, pause, FastRew, ..., you can go into the +replaymode by pressing menu -> blue. There you can control the livebuffer like +a normal replay. + +With the 'back'-key you jump to the position where you interrupted the live +replay. + +If 'Keep paused LiveBuffer' is activated, the livebuffer will be continued +after channel switch, when you had paused the replay before. +The 'stop'-key jumps to the end of the buffer and the livebuffer doesn't count +as paused anymore. + + +As the livevideo is continously recorded, it is possible to get the just seen +scene into a normal recording: + +- You can simply create a new timer. If the starttime is in the past, then the + already seen part of the recording will be added. + +- Or you rewind to the startpoint and start an instant recording (by pressing + the rec-key or menu -> red) + +- Of course you can cut the livebuffer like a normal recording. Press '0' for + setting the marks and '2' starts the cutting proccess. (You have to be in + the replaymode for this) + + diff -Naur vdr-1.5.8.org/receiver.c vdr-1.5.8/receiver.c --- vdr-1.5.8.org/receiver.c 2007-08-12 07:52:59.000000000 -0400 +++ vdr-1.5.8/receiver.c 2008-01-23 17:09:36.428981060 -0500 @@ -18,6 +18,17 @@ channelID = ChannelID; priority = Priority; numPids = 0; + SetPIDs(Pid,Pids1,Pids2,Pids3); +} + +cReceiver::~cReceiver() +{ + Detach(); +} + +void cReceiver::SetPIDs(int Pid, const int *Pids1, const int *Pids2, const int *Pids3) +{ + numPids = 0; if (Pid) pids[numPids++] = Pid; if (Pids1) { @@ -36,16 +47,6 @@ dsyslog("too many PIDs in cReceiver"); } -cReceiver::~cReceiver() -{ - if (device) { - const char *msg = "ERROR: cReceiver has not been detached yet! This is a design fault and VDR will segfault now!"; - esyslog(msg); - fprintf(stderr, "%s\n", msg); - *(char *)0 = 0; // cause a segfault - } -} - bool cReceiver::WantsPid(int Pid) { if (Pid) { diff -Naur vdr-1.5.8.org/receiver.h vdr-1.5.8/receiver.h --- vdr-1.5.8.org/receiver.h 2007-01-05 06:00:36.000000000 -0500 +++ vdr-1.5.8/receiver.h 2008-01-23 17:09:36.428981060 -0500 @@ -16,6 +16,8 @@ class cReceiver { friend class cDevice; + friend class cLiveRecorder; + friend class cLiveRecorderControl; private: cDevice *device; tChannelID channelID; @@ -25,6 +27,7 @@ bool WantsPid(int Pid); protected: void Detach(void); + void SetPIDs(int Pid, const int *Pids1 = NULL, const int *Pids2 = NULL, const int *Pids3 = NULL); virtual void Activate(bool On) {} ///< This function is called just before the cReceiver gets attached to ///< (On == true) or detached from (On == false) a cDevice. It can be used diff -Naur vdr-1.5.8.org/recorder.c vdr-1.5.8/recorder.c --- vdr-1.5.8.org/recorder.c 2008-01-23 17:07:57.091788613 -0500 +++ vdr-1.5.8/recorder.c 2008-01-23 17:28:48.168627695 -0500 @@ -12,6 +12,7 @@ #include #include #include "shutdown.h" +#include "cutter.h" #define RECORDERBUFSIZE MEGABYTE(5) @@ -24,27 +25,13 @@ // --- cFileWriter ----------------------------------------------------------- -class cFileWriter : public cThread { -private: - cRemux *remux; - cFileName *fileName; - cIndexFile *index; - uchar pictureType; - int fileSize; - cUnbufferedFile *recordFile; - time_t lastDiskSpaceCheck; - bool RunningLowOnDiskSpace(void); - bool NextFile(void); -protected: - virtual void Action(void); -public: - cFileWriter(const char *FileName, cRemux *Remux); - virtual ~cFileWriter(); - }; - -cFileWriter::cFileWriter(const char *FileName, cRemux *Remux) +cFileWriter::cFileWriter(const char *FileName, cRemux *Remux, bool IsLiveRec, bool IsBeginning) :cThread("file writer") { + isLiveRec = IsLiveRec; + isBeginning=IsBeginning; + ResumeLiveRec=false; + filename=strdup(FileName); fileName = NULL; remux = Remux; index = NULL; @@ -56,6 +43,7 @@ if (!recordFile) return; // Create the index file: + if (!IsBeginning) index = new cIndexFile(FileName, true); if (!index) esyslog("ERROR: can't allocate index"); @@ -67,6 +55,7 @@ Cancel(3); delete index; delete fileName; + free(filename); } bool cFileWriter::RunningLowOnDiskSpace(void) @@ -74,7 +63,7 @@ if (time(NULL) > lastDiskSpaceCheck + DISKCHECKINTERVAL) { int Free = FreeDiskSpaceMB(fileName->Name()); lastDiskSpaceCheck = time(NULL); - if (Free < MINFREEDISKSPACE) { + if (Free < MINFREEDISKSPACE && (!isLiveRec || Free == 0)) { dsyslog("low disk space (%d MB, limit is %d MB)", Free, MINFREEDISKSPACE); return true; } @@ -86,7 +75,12 @@ { if (recordFile && pictureType == I_FRAME) { // every file shall start with an I_FRAME if (fileSize > MEGABYTE(Setup.MaxVideoFileSize) || RunningLowOnDiskSpace()) { - recordFile = fileName->NextFile(); + if (isLiveRec && fileSize) + { + index->Write(NO_PICTURE, fileName->Number(), fileSize); + index->Write(NO_PICTURE, fileName->Number(), fileSize); + } + recordFile = fileName->SetOffset(fileName->Number()+1,0,isLiveRec && fileName->Number()>=0); fileSize = 0; } } @@ -98,10 +92,32 @@ time_t t = time(NULL); while (Running()) { int Count; + u_int64_t PTS; uchar *p = remux->Get(Count, &pictureType); + if (ResumeLiveRec && p && pictureType == I_FRAME && pts<(PTS=((u_int64_t)(p[9]&0x0E)<<29)+((u_int64_t)p[10]<<22)+((u_int64_t)(p[11]&0xFE)<<14)+((u_int64_t)p[12]<<7)+((u_int64_t)(p[13]&0xFE)>>1) ) ) + { + while (cCutter::Active()) + usleep(1); + index = new cIndexFile(filename, true); + ResumeLiveRec=false; + isBeginning=false; + } + if (isBeginning && p) + { + remux->Del(Count); + p=NULL; + t = time(NULL); + } if (p) { if (!Running() && pictureType == I_FRAME) // finish the recording before the next 'I' frame break; + if (isLiveRec && (fileSize/1024/1024)>=(Setup.LiveBufferSize-Setup.MaxVideoFileSize*(fileName->Number()-1)) && pictureType == I_FRAME) + { + index->Write(NO_PICTURE, fileName->Number(), fileSize); + index->Write(NO_PICTURE, fileName->Number(), fileSize); + recordFile = fileName->SetOffset(1,fileSize=0,isLiveRec); + recordFile->Seek(0,0); + } if (NextFile()) { if (index && pictureType != NO_PICTURE) index->Write(pictureType, fileName->Number(), fileSize); @@ -116,7 +132,7 @@ break; t = time(NULL); } - else if (time(NULL) - t > MAXBROKENTIMEOUT) { + else if (!isLiveRec && time(NULL) - t > MAXBROKENTIMEOUT) { esyslog("ERROR: video data stream broken"); ShutdownHandler.RequestEmergencyExit(); t = time(NULL); @@ -124,20 +140,31 @@ } } +void cFileWriter::RecordNextIFrame(u_int64_t PTS) +{ + recordFile = fileName->NextFile(); + fileSize = 0; + ResumeLiveRec = true; + pts=PTS; +} + // --- cRecorder ------------------------------------------------------------- -cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids) +cRecorder::cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids, bool IsLiveRec, bool IsBeginning) :cReceiver(ChannelID, Priority, VPID_FROM_ANY(VPid), APids, Setup.UseDolbyDigital ? DPids : NULL, SPids) ,cThread("recording") { + isLiveRec=IsLiveRec; + attached=false; // Make sure the disk is up and running: SpinUpDisk(FileName); ringBuffer = new cRingBufferLinear(RECORDERBUFSIZE, TS_SIZE * 2, true, "Recorder"); ringBuffer->SetTimeouts(0, 100); - remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, true); - writer = new cFileWriter(FileName, remux); + remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, !isLiveRec); + oldRemux=NULL; + writer = new cFileWriter(FileName, remux,IsLiveRec,IsBeginning); } cRecorder::~cRecorder() @@ -145,6 +172,8 @@ Detach(); delete writer; delete remux; + if (oldRemux) + delete (oldRemux); delete ringBuffer; } @@ -153,20 +182,35 @@ if (On) { writer->Start(); Start(); + attached=true; } - else + else { + attached=false; Cancel(3); } +} void cRecorder::Receive(uchar *Data, int Length) { - if (Running()) { + if (Running() || isLiveRec) { int p = ringBuffer->Put(Data, Length); if (p != Length && Running()) ringBuffer->ReportOverflow(Length - p); } } +void cRecorder::ChangePIDs(cDevice *Device, int VPid, const int *APids, const int *DPids, const int *SPids) +{ + Detach(); + SetPIDs(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids); + Device->AttachReceiver(this); + if (oldRemux) + delete oldRemux; + oldRemux = remux; + writer->SetNewRemux(remux = new cRemux(VPid, APids, Setup.UseDolbyDigital ? DPids : NULL, SPids, !isLiveRec)); + Start(); +} + void cRecorder::Action(void) { while (Running()) { @@ -181,3 +225,4 @@ } } } + diff -Naur vdr-1.5.8.org/recorder.h vdr-1.5.8/recorder.h --- vdr-1.5.8.org/recorder.h 2007-01-05 05:44:05.000000000 -0500 +++ vdr-1.5.8/recorder.h 2008-01-23 17:09:36.430981204 -0500 @@ -16,22 +16,51 @@ #include "ringbuffer.h" #include "thread.h" -class cFileWriter; +class cFileWriter : public cThread { +friend class cCuttingThread; +private: + bool isLiveRec; + bool isBeginning; + bool ResumeLiveRec; + char* filename; + u_int64_t pts; + cRemux *remux; + cFileName *fileName; + cIndexFile *index; + uchar pictureType; + int fileSize; + cUnbufferedFile *recordFile; + time_t lastDiskSpaceCheck; + bool RunningLowOnDiskSpace(void); + bool NextFile(void); +protected: + virtual void Action(void); +public: + cFileWriter(const char *FileName, cRemux *Remux,bool IsLiveRec = false, bool IsBeginning = false); + virtual ~cFileWriter(); + void SetNewRemux(cRemux *Remux) {remux=Remux;} + void RecordNextIFrame(u_int64_t PTS); + }; class cRecorder : public cReceiver, cThread { private: + bool isLiveRec; + bool attached; cRingBufferLinear *ringBuffer; - cRemux *remux; + cRemux *remux, *oldRemux; cFileWriter *writer; protected: virtual void Activate(bool On); virtual void Receive(uchar *Data, int Length); virtual void Action(void); public: - cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids); + cRecorder(const char *FileName, tChannelID ChannelID, int Priority, int VPid, const int *APids, const int *DPids, const int *SPids, bool IsLiveRec = false, bool IsBeginning = false); // Creates a new recorder for the channel with the given ChannelID and // the given Priority that will record the given PIDs into the file FileName. virtual ~cRecorder(); + void ChangePIDs(cDevice *Device, int VPid, const int *APids, const int *DPids, const int *SPids); + cFileWriter *GetWriter() {return writer;} + bool IsAttached() {return attached;} }; #endif //__RECORDER_H diff -Naur vdr-1.5.8.org/recording.c vdr-1.5.8/recording.c --- vdr-1.5.8.org/recording.c 2008-01-23 17:07:57.100789265 -0500 +++ vdr-1.5.8/recording.c 2008-01-23 17:09:36.431981277 -0500 @@ -739,8 +739,18 @@ return titleBuffer; } -const char *cRecording::PrefixFileName(char Prefix) +const char *cRecording::PrefixFileName(char Prefix, bool IsLiveRec) { + if (IsLiveRec) + { + free(name); + name=strdup(strrchr(fileName,'/')+1); + start = time(NULL); + priority=50; + lifetime=50; + free(fileName); + fileName=NULL; + } cString p = PrefixVideoFileName(FileName(), Prefix); if (*p) { free(fileName); @@ -1134,8 +1144,6 @@ //XXX+ somewhere else??? // --- cIndexFile ------------------------------------------------------------ -#define INDEXFILESUFFIX "/index.vdr" - // The number of frames to stay off the end in case of time shift: #define INDEXSAFETYLIMIT 150 // frames @@ -1145,9 +1153,11 @@ // The minimum age of an index file for considering it no longer to be written: #define MININDEXAGE 3600 // seconds -cIndexFile::cIndexFile(const char *FileName, bool Record) +cIndexFile::cIndexFile(const char *FileName, bool Record, bool IsLiveRec) :resumeFile(FileName) { + isLiveRec = IsLiveRec; + oldLast=oldFirst=0; f = -1; fileName = NULL; size = 0; @@ -1266,6 +1276,8 @@ } else LOG_ERROR_STR(fileName); + if (isLiveRec) + break; if (Index < last - (i ? 2 * INDEXSAFETYLIMIT : 0) || Index > 10 * INDEXSAFETYLIMIT) // keep off the end in case of "Pause live video" break; cCondWait::SleepMs(1000); @@ -1313,11 +1325,12 @@ int cIndexFile::GetNextIFrame(int Index, bool Forward, uchar *FileNumber, int *FileOffset, int *Length, bool StayOffEnd) { + int fIndex=GetFirstFrame(); if (CatchUp()) { int d = Forward ? 1 : -1; for (;;) { Index += d; - if (Index >= 0 && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) { + if (Index >= 0 && (Forward || Index>=fIndex ) && Index < last - ((Forward && StayOffEnd) ? INDEXSAFETYLIMIT : 0)) { if (index[Index].type == I_FRAME) { if (FileNumber) *FileNumber = index[Index].number; @@ -1348,6 +1361,49 @@ return -1; } +int cIndexFile::GetFirstFrame() +{ + if (last==oldLast) + return oldFirst; + if (oldLast) + { + do + { + oldLast++; + while (index[oldLast].number == index[oldFirst].number && (unsigned) index[oldLast].offset <= (unsigned) index[oldFirst].offset + && ((unsigned) index[oldLast].offset + MAXFRAMESIZE) > (unsigned) index[oldFirst].offset) + oldFirst++; + } while(oldLast!=last); + return oldFirst; + } + int Index=oldLast=last; + int offs[256]; + memset(offs,0,256); + offs[index[Index].number]=index[Index].offset; + if (index[Index].offset==index[Index-1].offset) + Index--; + do + { + if (index && !offs[index[Index].number] && index[Index].offset==index[Index-1].offset) + { + offs[index[Index].number]=index[Index].offset; + Index--; + } + Index--; + } while ((Index>=0) && (!offs[index[Index].number] || index[Index].offset < offs[index[Index].number] || (unsigned) index[Index].offset > (unsigned) (offs[index[Index].number] + MAXFRAMESIZE))); + return oldFirst=Index+1; +} + +int cIndexFile::GetLast() +{ + struct stat buf; + if (fstat(f, &buf)) { + LOG_ERROR_STR(fileName); + return -1; + } + return buf.st_size / sizeof(tIndex) - 1; +} + int cIndexFile::Get(uchar FileNumber, int FileOffset) { if (CatchUp()) { @@ -1433,15 +1489,36 @@ } } -cUnbufferedFile *cFileName::SetOffset(int Number, int Offset) +cUnbufferedFile *cFileName::SetOffset(int Number, int Offset, bool overwrite) { if (fileNumber != Number) Close(); + else if (overwrite) + return file; if (0 < Number && Number <= MAXFILESPERRECORDING) { fileNumber = Number; sprintf(pFileNumber, RECORDFILESUFFIX, fileNumber); if (record) { if (access(fileName, F_OK) == 0) { + if (overwrite) + { + int size = strlen(fileName) * 2; + char *l = MALLOC(char, size); + int n = readlink(fileName, l, size); + if (n<0) { + if (errno != EINVAL) + LOG_ERROR_STR(fileName); + file = cUnbufferedFile::Create(fileName, O_RDWR | O_CREAT | (blocking ? 0 : O_NONBLOCK), DEFFILEMODE); + } + else if (n < size) { + l[n]=0; + file = cUnbufferedFile::Create(l, O_RDWR | O_CREAT | (blocking ? 0 : O_NONBLOCK), DEFFILEMODE); + } + free(l); + return file; + } + else + { // files exists, check if it has non-zero size struct stat buf; if (stat(fileName, &buf) == 0) { @@ -1456,6 +1533,7 @@ else return SetOffset(Number + 1); // error with fstat - should not happen, just to be on the safe side } + } else if (errno != ENOENT) { // something serious has happened LOG_ERROR_STR(fileName); return NULL; diff -Naur vdr-1.5.8.org/recording.h vdr-1.5.8/recording.h --- vdr-1.5.8.org/recording.h 2008-01-23 17:07:57.108789844 -0500 +++ vdr-1.5.8/recording.h 2008-01-23 17:09:36.470984100 -0500 @@ -40,6 +40,7 @@ class cRecordingInfo { friend class cRecording; + friend class cLiveRecorder; private: tChannelID channelID; char *channelName; @@ -88,7 +89,7 @@ const char *FileName(void) const; const char *Title(char Delimiter = ' ', bool NewIndicator = false, int Level = -1) const; const cRecordingInfo *Info(void) const { return info; } - const char *PrefixFileName(char Prefix); + const char *PrefixFileName(char Prefix, bool IsLiveRec = false); int HierarchyLevels(void) const; void ResetResume(void) const; bool IsNew(void) const { return GetResume() <= 0; } @@ -178,6 +179,7 @@ //XXX+ #define FRAMESPERSEC 25 +#define INDEXFILESUFFIX "/index.vdr" // The maximum size of a single frame (up to HDTV 1920x1080): #define MAXFRAMESIZE KILOBYTE(512) @@ -193,6 +195,8 @@ class cIndexFile { private: + bool isLiveRec; + int oldFirst,oldLast; struct tIndex { int offset; uchar type; uchar number; short reserved; }; int f; char *fileName; @@ -202,14 +206,16 @@ cMutex mutex; bool CatchUp(int Index = -1); public: - cIndexFile(const char *FileName, bool Record); + cIndexFile(const char *FileName, bool Record,bool IsLiveRec = false); ~cIndexFile(); bool Ok(void) { return index != NULL; } bool Write(uchar PictureType, uchar FileNumber, int FileOffset); bool Get(int Index, uchar *FileNumber, int *FileOffset, uchar *PictureType = NULL, int *Length = NULL); int GetNextIFrame(int Index, bool Forward, uchar *FileNumber = NULL, int *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false); + int GetFirstFrame(); int Get(uchar FileNumber, int FileOffset); int Last(void) { CatchUp(); return last; } + int GetLast(void); int GetResume(void) { return resumeFile.Read(); } bool StoreResume(int Index) { return resumeFile.Save(Index); } bool IsStillRecording(void); @@ -229,7 +235,7 @@ int Number(void) { return fileNumber; } cUnbufferedFile *Open(void); void Close(void); - cUnbufferedFile *SetOffset(int Number, int Offset = 0); + cUnbufferedFile *SetOffset(int Number, int Offset = 0, bool overwrite = false); cUnbufferedFile *NextFile(void); }; diff -Naur vdr-1.5.8.org/ringbuffer.c vdr-1.5.8/ringbuffer.c --- vdr-1.5.8.org/ringbuffer.c 2006-06-16 05:32:13.000000000 -0400 +++ vdr-1.5.8/ringbuffer.c 2008-01-23 17:09:36.479984752 -0500 @@ -72,7 +72,7 @@ void cRingBuffer::EnableGet(void) { - if (getTimeout && Available() > Size() / 3) + if (getTimeout && Available() > 0) //Size() / 3) readyForGet.Signal(); } diff -Naur vdr-1.5.8.org/timers.h vdr-1.5.8/timers.h --- vdr-1.5.8.org/timers.h 2007-06-03 09:24:58.000000000 -0400 +++ vdr-1.5.8/timers.h 2008-01-23 17:09:36.479984752 -0500 @@ -26,6 +26,8 @@ class cTimer : public cListObject { friend class cMenuEditTimer; + friend class cReplayControl; + friend class cLiveRecorderControl; private: mutable time_t startTime, stopTime; time_t lastSetEvent; diff -Naur vdr-1.5.8.org/transfer.c vdr-1.5.8/transfer.c --- vdr-1.5.8.org/transfer.c 2008-01-23 17:07:57.202796653 -0500 +++ vdr-1.5.8/transfer.c 2008-01-23 17:09:36.480984824 -0500 @@ -8,6 +8,8 @@ */ #include "transfer.h" +#include "videodir.h" +#include "menu.h" #define TRANSFERBUFSIZE MEGABYTE(2) #define POLLTIMEOUTS_BEFORE_DEVICECLEAR 6 @@ -107,6 +109,334 @@ } } +// --- cLiveRecorder --------------------------------------------------------- + +cLiveRecorder *cLiveRecorderControl::liveRecorder[MAXLIVERECORDERS] = {NULL}; +cLiveRecorder *cLiveRecorder::ActiveLiveRecorder = NULL; +cLiveRecorderControl *cLiveRecorderControl::LiveThread = NULL; + +cLiveRecorder::cLiveRecorder(cDevice *ReceiverDevice, const cChannel *Channel) +{ + start = time(NULL); + New = true; + receiverDevice = ReceiverDevice; + channelID = tChannelID::tChannelID(Channel->Source(),Channel->Nid(),Channel->Tid(),Channel->Sid(),Channel->Rid()); + Pids.VPid=Channel->Vpid(); + for (int i=0; i<=MAXAPIDS; i++) + Pids.APids[i] = Channel->Apid(i); + for (int i=0; i<=MAXDPIDS; i++) + Pids.DPids[i] = Channel->Dpid(i); + for (int i=0; i<=MAXSPIDS; i++) + Pids.SPids[i] = Channel->Spid(i); + Rew=false; + sprintf(FileName,"%s/LiveBuffer/%s",BufferDirectory,Channel->Name()); + while (DirectoryOk(FileName,false)) + sprintf(FileName,"%s_",FileName); + MakeDirs(FileName,true); + Recorder=new cRecorder(FileName, channelID, -1,Channel->Vpid(), Channel->Apids(),Channel->Dpids(),Channel->Spids(),true); + while (!cLiveRecorderControl::LiveThread->wantadd && !ReceiverDevice->HasLock()) + usleep(100); + if (!cLiveRecorderControl::LiveThread->wantadd) + ReceiverDevice->AttachReceiver(Recorder); + WriteInfo(); + while (cControl::Control() && !cControl::Control()->Attached() && !cLiveRecorderControl::LiveThread->wantadd) + usleep(100); + Text(text); + cReplayControl::SetRecording(FileName,text, true); + replay=NULL; + if (cDevice::PrimaryDevice()->Replaying() && !cControl::Control()) + return; + if (!cLiveRecorderControl::LiveThread->wantadd) + cControl::Launch(replay = new cReplayControl(true,this)); +} + +cLiveRecorder::~cLiveRecorder() +{ + delete Recorder; + RemoveFileOrDir(FileName,true); +} + +bool cLiveRecorder::PIDsChanged(const cChannel *Channel) +{ + bool changed=false; + if (Pids.VPid != Channel->Vpid()) + changed=true; + Pids.VPid=Channel->Vpid(); + for (int i=0; i<=MAXAPIDS; i++) + { + if (Pids.APids[i] != Channel->Apid(i)) + changed=true; + Pids.APids[i]=Channel->Apid(i); + } + for (int i=0; i<=MAXDPIDS; i++) + { + if (Pids.DPids[i] != Channel->Dpid(i)) + changed=true; + Pids.DPids[i]=Channel->Dpid(i); + } + for (int i=0; i<=MAXSPIDS; i++) + { + if (Pids.SPids[i] != Channel->Spid(i)) + changed=true; + Pids.SPids[i]=Channel->Spid(i); + } + return changed; +} + +void cLiveRecorder::Text(char *Name) +{ + strcpy(Name,"LiveBuffer"); + bool any=false; + for (int i=0; ichannelID)->Number()); + any=true; + } + if (any) + sprintf(Name,"%s)",Name); +} + +void cLiveRecorder::rew(bool On) +{ + if (!Setup.KeepBuffer) + return; + if (!On) + Recorder->priority = -1; + Rew=On; + replay->Rew(On); +} + +bool cLiveRecorder::IsAlreadyRecording(tChannelID ChannelID) +{ + return channelID==ChannelID; +} + +void cLiveRecorder::Resume() +{ + if (Recorder->IsAttached()) + { + while (!cLiveRecorderControl::LiveThread->wantadd && !receiverDevice->HasLock()) + usleep(100); + if (!cLiveRecorderControl::LiveThread->wantadd) + receiverDevice->AttachReceiver(Recorder); + } + Text(text); + cReplayControl::SetRecording(FileName,text,true); + cControl::Launch(replay = new cReplayControl(true,this)); + if (Recorder->priority != -2) + { + Recorder->priority = -1; + New = true; + } + if (Rew) + replay->Rew(true); +} + +void cLiveRecorder::WriteInfo() +{ + cRecordingInfo* info = new cRecordingInfo(Channels.GetByChannelID(channelID)); + char *InfoFileName = NULL; + asprintf(&InfoFileName, "%s/%s", FileName, "info.vdr"); + FILE *f = fopen(InfoFileName, "w"); + if (f) { + info->Write(f); + fclose(f); + } + free(InfoFileName); +} + +// --- cLiveRecorderControl -------------------------------------------------- + +cLiveRecorderControl::cLiveRecorderControl() +{ + wantadd=false; + add=false; + Start(); +} + +void cLiveRecorderControl::ClearBufferDirectory() +{ + char FileName[255]; + sprintf(FileName,"%s/LiveBuffer",BufferDirectory); + cReadDir d(FileName); + struct dirent *e; + while ((e=d.Next()) != NULL) { + if (strcmp(e->d_name, ".") && strcmp(e->d_name, "..")) { + char *buffer; + asprintf(&buffer, "%s/%s", FileName, e->d_name); + RemoveFileOrDir(buffer,true); + } + } +} + +void cLiveRecorderControl::Action() +{ + ClearBufferDirectory(); + while (Running()) + { + if (!add) + { + wantadd=false; + usleep(100); + continue; + } + cLiveRecorder *OldRecorder=NULL,*OldActive=cLiveRecorder::ActiveLiveRecorder; + cLiveRecorder::ActiveLiveRecorder=NULL; + for (int i=0; iRecorder->IsAttached() || (!liveRecorder[i]->IsAlreadyRecording(tChannelID::tChannelID(Channel->Source(),Channel->Nid(),Channel->Tid(),Channel->Sid(),Channel->Rid())) && liveRecorder[i]->Recorder->priority==-1)) + { + if (!OldRecorder && !liveRecorder[i]->IsAlreadyRecording(tChannelID::tChannelID(Channel->Source(),Channel->Nid(),Channel->Tid(),Channel->Sid(),Channel->Rid()))) + OldRecorder=liveRecorder[i]; + else + delete liveRecorder[i]; + liveRecorder[i]=NULL; + } + } + if (liveRecorder[i] && liveRecorder[i]->IsAlreadyRecording(tChannelID::tChannelID(Channel->Source(),Channel->Nid(),Channel->Tid(),Channel->Sid(),Channel->Rid()))) + cLiveRecorder::ActiveLiveRecorder = liveRecorder[i]; + } + if (cLiveRecorder::ActiveLiveRecorder) + { + if (cLiveRecorder::ActiveLiveRecorder->PIDsChanged(Channel)) + { + cLiveRecorder::ActiveLiveRecorder->Recorder->ChangePIDs(ReceiverDevice,Channel->Vpid(), Channel->Apids(),Channel->Dpids(),Channel->Spids()); + cLiveRecorder::ActiveLiveRecorder->WriteInfo(); + } + if (cLiveRecorder::ActiveLiveRecorder != OldActive || !OldActive->replay || !cDevice::PrimaryDevice()->Replaying()) + cLiveRecorder::ActiveLiveRecorder->Resume(); + } + else + { + int i; + for (i=0; iwantadd=true; + while (LiveThread->wantadd) + usleep(10); + LiveThread->ReceiverDevice=ReceiverDevice; + LiveThread->Channel=Channel; + LiveThread->add=true; +} + +void cLiveRecorderControl::Shutdown() +{ + if (LiveThread) + LiveThread->Cancel(3); + LiveThread=NULL; +} + +cLiveRecorder* cLiveRecorderControl::GetLiveRecorder(const cChannel *Channel) +{ + if (Channel) + { + for (int i=0; iIsAlreadyRecording(tChannelID::tChannelID(Channel->Source(),Channel->Nid(),Channel->Tid(),Channel->Sid(),Channel->Rid()))) + { + return liveRecorder[i]; + break; + } + return NULL; + } + else + return cLiveRecorder::ActiveLiveRecorder; +} + +void cLiveRecorderControl::UpdateTimes() +{ + if (cControl::Control() && cControl::Control()->IsLiveRecording() && !LiveThread->add) + { + bool play,forward; + int speed; + cControl::Control()->GetReplayMode(play,forward,speed); + if (!play && cLiveRecorder::ActiveLiveRecorder->Rew) + cLiveRecorder::ActiveLiveRecorder->Recorder->priority=-2; + } + int l=-1; + if (Setup.KeepLastBuffer) + for (int i=0; iRecorder->priority==-1 && liveRecorder[i]->New && time(NULL)-liveRecorder[i]->start > Setup.BufferTimeOut) + { + liveRecorder[i]->Recorder->priority = -3; + liveRecorder[i]->New = false; + l=i; + break; + } + if (l>=0) + for (int i=0; iRecorder->priority=liveRecorder[i]->Recorder->priority < -2 ? liveRecorder[i]->Recorder->priority > -Setup.NumLastBuffers - 2 ? liveRecorder[i]->Recorder->priority - 1 : -1 : liveRecorder[i]->Recorder->priority; +} + +bool cLiveRecorderControl::InLiveBuffer(cTimer *timer) +{ + if (timer && cLiveRecorderControl::GetLiveRecorder(timer->Channel()) && !timer->Recording()) + { + cIndexFile *index = new cIndexFile(cLiveRecorderControl::GetLiveRecorder(timer->Channel())->GetFileName(),false); + if (!index) + return false; + int now=index->GetLast() - 1; + int beginning=index->GetFirstFrame(); + delete index; + if (timer->StartTime() < time(NULL) && now-(time(NULL)-timer->StopTime())*FRAMESPERSEC > beginning) + return true; + } + return false; +} + +int cLiveRecorderControl::MatchesEvent(const cEvent *event) +{ + cLiveRecorder *liverecorder=cLiveRecorderControl::GetLiveRecorder(Channels.GetByChannelID(event->ChannelID())); + if (!liverecorder || time(NULL)StartTime()) + return 0; + cIndexFile *index = new cIndexFile(liverecorder->GetFileName(),false); + if (!index) + return 0; + int m=0; + if ((time(NULL)-index->GetLast()/FRAMESPERSEC)EndTime()) { + int starttime=time(NULL)-(index->GetLast()-index->GetFirstFrame())/FRAMESPERSEC; + if (starttime<=event->StartTime() && time(NULL)>event->StartTime() || starttime<=event->EndTime() && time(NULL)>event->StartTime()) + m=1; + if (starttime<=event->StartTime() && time(NULL)>=event->StartTime()) + m=2; + } + delete index; + return m; +} + // --- cTransferControl ------------------------------------------------------ cDevice *cTransferControl::receiverDevice = NULL; diff -Naur vdr-1.5.8.org/transfer.h vdr-1.5.8/transfer.h --- vdr-1.5.8.org/transfer.h 2007-01-05 05:45:45.000000000 -0500 +++ vdr-1.5.8/transfer.h 2008-01-23 17:09:36.496985982 -0500 @@ -15,6 +15,7 @@ #include "remux.h" #include "ringbuffer.h" #include "thread.h" +#include "recorder.h" class cTransfer : public cReceiver, public cPlayer, public cThread { private: @@ -29,7 +30,69 @@ virtual ~cTransfer(); }; +class cReplayControl; + +#define MAXLIVERECORDERS 16 + +class cLiveRecorder { +friend class cLiveRecorderControl; +private: + struct PIDs + { + int VPid; + int APids[MAXAPIDS+1]; + int DPids[MAXDPIDS+1]; + int SPids[MAXSPIDS+1]; + } Pids; + static cLiveRecorder *ActiveLiveRecorder; + time_t start; + bool New; + bool Rew; + char text[256]; + tChannelID channelID; + cReplayControl *replay; + cDevice *receiverDevice; + char FileName[256]; + cRecorder* Recorder; + cLiveRecorder(cDevice *ReceiverDevice, const cChannel *Channel); + bool PIDsChanged(const cChannel *Channel); + void Text(char *Name); + void WriteInfo(); +public: + ~cLiveRecorder(); + void rew(bool On = true); + bool IsRew() { return Rew; } + const char* GetFileName(void) {return FileName;} + bool IsAlreadyRecording(tChannelID ChannelID); + cReplayControl* GetReplayControl() { return replay; } + void Resume(); + void ChangePriority(int prio) { Recorder->priority = prio; } +}; + +class cLiveRecorderControl : public cThread { +friend class cLiveRecorder; +private: + bool wantadd,add; + static cLiveRecorderControl *LiveThread; + static cLiveRecorder *liveRecorder[MAXLIVERECORDERS]; + cDevice *ReceiverDevice; + const cChannel *Channel; + cLiveRecorderControl(); + void ClearBufferDirectory(void); + virtual void Action(void); +public: + static void Add(cDevice *ReceiverDevice, const cChannel *Channel); + static void Shutdown(); + static cLiveRecorder* GetLiveRecorder(const cChannel *Channel = NULL); + static void UpdateTimes(void); + static bool HasProgramme(void) { return (LiveThread && LiveThread->add) || cControl::Control() && cControl::Control()->IsLiveRecording(); } + static bool InLiveBuffer(cTimer *timer); + static int MatchesEvent(const cEvent *event); +}; + class cTransferControl : public cControl { +friend class cLiveRecorderControl; +friend class cReplayControl; private: cTransfer *transfer; static cDevice *receiverDevice; diff -Naur vdr-1.5.8.org/vdr.c vdr-1.5.8/vdr.c --- vdr-1.5.8.org/vdr.c 2007-08-18 09:03:46.000000000 -0400 +++ vdr-1.5.8/vdr.c 2008-01-23 17:31:18.841592033 -0500 @@ -220,6 +220,7 @@ static struct option long_options[] = { { "audio", required_argument, NULL, 'a' }, + { "buffer", required_argument, NULL, 'b' }, { "config", required_argument, NULL, 'c' }, { "daemon", no_argument, NULL, 'd' }, { "device", required_argument, NULL, 'D' }, @@ -246,10 +247,14 @@ }; int c; - while ((c = getopt_long(argc, argv, "a:c:dD:E:g:hl:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { + while ((c = getopt_long(argc, argv, "a:b:c:dD:E:g:hl:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { switch (c) { case 'a': AudioCommand = optarg; break; + case 'b': BufferDirectory = optarg; + while (optarg && *optarg && optarg[strlen(optarg) - 1] == '/') + optarg[strlen(optarg) - 1] = 0; + break; case 'c': ConfigDirectory = optarg; break; case 'd': DaemonMode = true; break; @@ -382,6 +387,8 @@ if (DisplayHelp) { printf("Usage: vdr [OPTIONS]\n\n" // for easier orientation, this is column 80| " -a CMD, --audio=CMD send Dolby Digital audio to stdin of command CMD\n" + " -b DIR, --buffer=DIR use DIR as LiveBuffer directory (default is to write\n" + " it to the video directory)\n" " -c DIR, --config=DIR read config files from DIR (default is to read them\n" " from the video directory)\n" " -d, --daemon run in daemon mode\n" @@ -523,6 +530,7 @@ int PreviousChannel[2] = { 1, 1 }; int PreviousChannelIndex = 0; time_t LastChannelChanged = time(NULL); + time_t LastActivity = 0; int MaxLatencyTime = 0; bool InhibitEpgScan = false; bool IsInfoMenu = false; @@ -533,6 +541,9 @@ if (!PluginManager.LoadPlugins(true)) EXIT(2); + if (!BufferDirectory) + BufferDirectory = VideoDirectory; + // Configuration data: if (!ConfigDirectory) @@ -916,7 +927,7 @@ DELETE_MENU; if (!WasInfoMenu) { IsInfoMenu = true; - if (cControl::Control()) { + if (cControl::Control() && !cControl::Control()->IsLiveRecording()) { cControl::Control()->Hide(); Menu = cControl::Control()->GetInfo(); if (Menu) @@ -1020,7 +1031,7 @@ break; // Instant recording: case kRecord: - if (!cControl::Control()) { + if (!cControl::Control() || cControl::Control()->IsLiveRecording()) { if (cRecordControls::Start()) Skins.Message(mtInfo, tr("Recording started")); key = kNone; // nobody else needs to see this key @@ -1056,8 +1067,9 @@ default: break; } Interact = Menu ? Menu : cControl::Control(); // might have been closed in the mean time + eOSState state=osUnknown; if (Interact) { - eOSState state = Interact->ProcessKey(key); + state = Interact->ProcessKey(key); if (state == osUnknown && Interact != cControl::Control()) { if (ISMODELESSKEY(key) && cControl::Control()) { state = cControl::Control()->ProcessKey(key); @@ -1113,7 +1125,7 @@ default: ; } } - else { + if (!Interact || state==osBUnknown) { // Key functions in "normal" viewing mode: if (key != kNone && KeyMacros.Get(key)) { cRemote::PutMacro(key); @@ -1183,7 +1195,7 @@ ShutdownHandler.countdown.Cancel(); } - if (!Interact && !cRecordControls::Active() && !cCutter::Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT) { + if (!Interact && !cRecordControls::Active() && (!cRecordControls::Active() && !cCutter::Active() && !Interface->HasSVDRPConnection() && (Now - cRemote::LastActivity()) > ACTIVITYTIMEOUT)) { // Handle housekeeping tasks // Shutdown: @@ -1205,6 +1217,18 @@ ShutdownHandler.SetRetry(SHUTDOWNRETRY); } + // LiveBuffer: + if (Setup.LiveBuffer && cLiveRecorderControl::GetLiveRecorder() && Setup.MinLiveBufferInactivity && LastActivity > 1 && Now - LastActivity > Setup.MinLiveBufferInactivity * 60) + { + if (cControl::Control() && cControl::Control()->IsLiveRecording()) + { + Setup.LiveBuffer=false; + Channels.SwitchTo(cDevice::CurrentChannel()); + Setup.LiveBuffer=true; + } + else + cLiveRecorderControl::Shutdown(); + } // Disk housekeeping: RemoveDeletedRecordings(); cSchedules::Cleanup(); @@ -1232,6 +1256,7 @@ cRecordControls::Shutdown(); cCutter::Stop(); delete Menu; + cLiveRecorderControl::Shutdown(); cControl::Shutdown(); delete Interface; cOsdProvider::Shutdown(); diff -Naur vdr-1.5.8.org/videodir.c vdr-1.5.8/videodir.c --- vdr-1.5.8.org/videodir.c 2005-12-18 05:33:20.000000000 -0500 +++ vdr-1.5.8/videodir.c 2008-01-23 17:09:36.498986127 -0500 @@ -20,6 +20,7 @@ #include "tools.h" const char *VideoDirectory = VIDEODIR; +const char *BufferDirectory = NULL; class cVideoDirectory { private: @@ -106,17 +107,22 @@ cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags) { const char *ActualFileName = FileName; + bool SepBufferDir = false; // Incoming name must be in base video directory: if (strstr(FileName, VideoDirectory) != FileName) { + if (strstr(FileName, BufferDirectory) == FileName) + SepBufferDir = true; + else { esyslog("ERROR: %s not in %s", FileName, VideoDirectory); errno = ENOENT; // must set 'errno' - any ideas for a better value? return NULL; } + } // Are we going to create a new file? if ((Flags & O_CREAT) != 0) { cVideoDirectory Dir; - if (Dir.IsDistributed()) { + if (Dir.IsDistributed() && !SepBufferDir) { // Find the directory with the most free space: int MaxFree = Dir.FreeMB(); while (Dir.Next()) { diff -Naur vdr-1.5.8.org/videodir.h vdr-1.5.8/videodir.h --- vdr-1.5.8.org/videodir.h 2005-10-31 06:50:23.000000000 -0500 +++ vdr-1.5.8/videodir.h 2008-01-23 17:09:36.499986199 -0500 @@ -14,6 +14,7 @@ #include "tools.h" extern const char *VideoDirectory; +extern const char *BufferDirectory; cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags); int CloseVideoFile(cUnbufferedFile *File);