From patchwork Mon Jan 2 17:11:40 2012 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: Marc X-Patchwork-Id: 12919 Received: from mail.tu-berlin.de ([130.149.7.33]) by www.linuxtv.org with esmtp (Exim 4.72) (envelope-from ) id 1RhlQI-0001JR-M6 for vdr@linuxtv.org; Mon, 02 Jan 2012 18:12:12 +0100 X-tubIT-Incoming-IP: 46.105.63.121 Received: from 1.mo2.mail-out.ovh.net ([46.105.63.121] helo=mo2.mail-out.ovh.net) by mail.tu-berlin.de (exim-4.75/mailfrontend-3) with esmtp for id 1RhlQI-0005Tw-EE; Mon, 02 Jan 2012 18:11:46 +0100 Received: from mail21.ha.ovh.net (b9.ovh.net [213.186.33.59]) by mo2.mail-out.ovh.net (Postfix) with SMTP id A584FDC0FD6 for ; Mon, 2 Jan 2012 18:12:43 +0100 (CET) Received: from b0.ovh.net (HELO queueout) (213.186.33.50) by b0.ovh.net with SMTP; 2 Jan 2012 19:11:45 +0200 Received: from gob75-4-82-226-175-12.fbx.proxad.net (HELO ?192.168.0.10?) (mail?dev@ekass.net@82.226.175.12) by ns0.ovh.net with SMTP; 2 Jan 2012 19:11:41 +0200 Message-ID: <4F01E54C.4030808@ekass.net> Date: Mon, 02 Jan 2012 18:11:40 +0100 From: Marc User-Agent: Mozilla/5.0 (X11; Linux i686; rv:8.0) Gecko/20111214 Thunderbird/8.0 MIME-Version: 1.0 To: VDR Mailing List X-Ovh-Mailout: 178.32.228.2 (mo2.mail-out.ovh.net) References: <4EF10742.20100@hertell.com> <4EF10857.7070500@dressler.ca> <201112221836.53454.rollercoaster@reel-multimedia.com> <4EF39E76.4090105@hertell.com> <4EF3A1D9.6080807@ekass.net> <4EF473A6.2030308@ekass.net> <4F009ABC.2080901@hertell.com> <4F00A4BD.7040905@ekass.net> <4F00B7B6.1050609@hertell.com> In-Reply-To: <4F00B7B6.1050609@hertell.com> X-Ovh-Tracer-Id: 16522862608898603101 X-Ovh-Remote: 82.226.175.12 (gob75-4-82-226-175-12.fbx.proxad.net) X-Ovh-Local: 213.186.33.20 (ns0.ovh.net) X-OVH-SPAMSTATE: OK X-OVH-SPAMSCORE: 10 X-OVH-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeefkedrvdeiucetggdotefuucfrrhhofhhilhgvmecuqfggjfenuceurghilhhouhhtmecufedttdenucgfrhhlucfvnfffucdluddtmdenucfjughrpefkpfffhfgfggfvufhfjggtsehmtdgrrgdtfedunecuhfhrohhmpeforghrtgcuoehvughrsegvkhgrshhsrdhnvghtqeenucffohhmrghinhepphgrshhtvggsihhnrdgtohhmpdhlihhnuhigthhvrdhorhhgne X-Spam-Check: DONE|U 0.5/N X-VR-SPAMSTATE: OK X-VR-SPAMSCORE: 10 X-VR-SPAMCAUSE: gggruggvucftvghtrhhoucdtuddrfeefkedrvdeiucetggdotefuucfrrhhofhhilhgvmecuqfggjfenuceurghilhhouhhtmecufedttdenucgfrhhlucfvnfffucdluddtmdenucfjughrpefkpfffhfgfggfvufhfjggtsehmtdgrrgdtfedunecuhfhrohhmpeforghrtgcuoehvughrsegvkhgrshhsrdhnvghtqeenucffohhmrghinhepphgrshhtvggsihhnrdgtohhmpdhlihhnuhigthhvrdhorhhgne X-PMX-Version: 5.6.1.2065439, Antispam-Engine: 2.7.2.376379, Antispam-Data: 2012.1.2.170017 X-PMX-Spam: Gauge=IIIIIIII, Probability=8%, Report=' FROM_NAME_ONE_WORD 0.05, MIME_TEXT_ONLY_MP_MIXED 0.05, MSGID_ADDED_BY_MTA 0.05, BODYTEXTP_SIZE_3000_LESS 0, BODY_SIZE_10000_PLUS 0, __ANY_URI 0, __BAT_BOUNDARY 0, __CP_URI_IN_BODY 0, __CT 0, __CTYPE_HAS_BOUNDARY 0, __CTYPE_MULTIPART 0, __CTYPE_MULTIPART_MIXED 0, __HAS_MSGID 0, __MIME_TEXT_ONLY 0, __MIME_VERSION 0, __MOZILLA_MSGID 0, __SANE_MSGID 0, __SUBJ_ALPHA_END 0, __TO_MALFORMED_2 0, __URI_NS , __USER_AGENT 0' X-LSpam-Score: -1.9 (-) X-LSpam-Report: No, score=-1.9 required=5.0 tests=BAYES_00=-1.9 autolearn=ham Subject: [vdr] Updated patch for vdr 1.7.22 in gentoo X-BeenThere: vdr@linuxtv.org X-Mailman-Version: 2.1.13 Precedence: list Reply-To: VDR Mailing List List-Id: VDR Mailing List List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , X-List-Received-Date: Mon, 02 Jan 2012 17:12:13 -0000 Status: O X-Status: X-Keywords: X-UID: 25530 On 01/01/2012 20:44, René wrote: > Hi Marc, > > I pasted the output over here: http://pastebin.com/8eMsEuMf > > It looks that it can't find the files to patch. Maybe the patch was > not compatible with Gentoo? > > It would be great if you could send an updated ebuild to 1.7.22 :-) I > really miss the days when i updated my vdr on every release.. Now i > have been "stuck" with 1.6.x for ages.. > > Do you think that it would be possible to add the livebuffer-patch > back to the official gentoo overlay? Even better if it could be added > as a basic feature of vdr (that could be turned on/off from the > settings :-) Klaus? Please, please, pleaaaaase :-) > > > Regards, > > René > > _______________________________________________ > vdr mailing list > vdr@linuxtv.org > http://www.linuxtv.org/cgi-bin/mailman/listinfo/vdr The patch applies, portage try several levels with patch and it succeeded at p1 (log start at 255). The modifications I've made work but you have more files that fail than me. Perhaps you use more use flags. I don't know if the extension patches are applied all the time or only when you set the use flag for them. You could try to build vdr without any use flag and see if it compiles. I attached the patch for 1.7.22 and I removed the ifdef statement in Makefile so there is no more need to patch the ebuild, just to put the patch in VDR_LOCAL_PATCHES_DIR. You can still create a folder for each version ( http://linuxtv.org/vdrwiki/index.php/Gentoo_VDR_own_patches#Putting_patches_there ) To add this patch to the overlay, have you tried to contact the maintainer ? It could be interested, its address is in the changelog. Regards, Marc. diff -Naur vdr-1.7.22/Makefile vdr-1.7.22-livebuffer/Makefile --- vdr-1.7.22/Makefile 2011-12-04 15:41:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/Makefile 2011-12-31 11:16:23.000000000 +0100 @@ -62,6 +62,9 @@ LIBS += $(shell pkg-config --libs fribidi) endif +DEFINES += -DUSE_LIVEBUFFER +OBJS += livebuffer.o + LIRC_DEVICE ?= /var/run/lirc/lircd RCU_DEVICE ?= /dev/ttyS1 diff -Naur vdr-1.7.22/config.c vdr-1.7.22-livebuffer/config.c --- vdr-1.7.22/config.c 2011-12-03 16:21:30.000000000 +0100 +++ vdr-1.7.22-livebuffer/config.c 2011-12-31 11:16:23.000000000 +0100 @@ -461,6 +461,10 @@ InitialChannel = ""; DeviceBondings = ""; InitialVolume = -1; +#ifdef USE_LIVEBUFFER + LiveBufferSize = 30; + LiveBufferMaxFileSize = 100; +#endif /*USE_LIVEBUFFER*/ ChannelsWrap = 0; EmergencyExit = 1; } @@ -655,6 +659,10 @@ else if (!strcasecmp(Name, "InitialChannel")) InitialChannel = Value; else if (!strcasecmp(Name, "InitialVolume")) InitialVolume = atoi(Value); else if (!strcasecmp(Name, "DeviceBondings")) DeviceBondings = Value; +#ifdef USE_LIVEBUFFER + else if (!strcasecmp(Name, "LiveBufferSize")) LiveBufferSize = atoi(Value); + else if (!strcasecmp(Name, "LiveBufferMaxFileSize")) LiveBufferMaxFileSize = atoi(Value); +#endif /*USE_LIVEBUFFER*/ else if (!strcasecmp(Name, "ChannelsWrap")) ChannelsWrap = atoi(Value); else if (!strcasecmp(Name, "EmergencyExit")) EmergencyExit = atoi(Value); else @@ -752,6 +760,9 @@ Store("InitialChannel", InitialChannel); Store("InitialVolume", InitialVolume); Store("DeviceBondings", DeviceBondings); +#ifdef USE_LIVEBUFFER + Store("LiveBufferSize", LiveBufferSize); +#endif /* LIVEBUFFER */ Store("ChannelsWrap", ChannelsWrap); Store("EmergencyExit", EmergencyExit); diff -Naur vdr-1.7.22/config.h vdr-1.7.22-livebuffer/config.h --- vdr-1.7.22/config.h 2011-12-03 15:19:52.000000000 +0100 +++ vdr-1.7.22-livebuffer/config.h 2011-12-31 11:16:23.000000000 +0100 @@ -307,6 +307,10 @@ int CurrentVolume; int CurrentDolby; int InitialVolume; +#ifdef USE_LIVEBUFFER + int LiveBufferSize; + int LiveBufferMaxFileSize; +#endif /*USE_LIVEBUFFER*/ int ChannelsWrap; int EmergencyExit; int __EndData__; diff -Naur vdr-1.7.22/device.c vdr-1.7.22-livebuffer/device.c --- vdr-1.7.22/device.c 2011-10-16 16:36:43.000000000 +0200 +++ vdr-1.7.22-livebuffer/device.c 2011-12-31 11:16:23.000000000 +0100 @@ -18,6 +18,10 @@ #include "receiver.h" #include "status.h" #include "transfer.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#include "interface.h" +#endif /*USE_LIVEBUFFER*/ // --- cLiveSubtitle --------------------------------------------------------- @@ -663,6 +667,14 @@ return false; case scrNoTransfer: Skins.Message(mtError, tr("Can't start Transfer Mode!")); return false; +#ifdef USE_LIVEBUFFER + case srcStillWritingLiveBuffer: + if(Interface->Confirm(tr("Still writing timeshift data to recording. Abort?"))) + cRecordControls::CancelWritingBuffer(); + else + if(cRecordControls::IsWritingBuffer()) return false; + break; +#endif /*USE_LIVEBUFFER*/ case scrFailed: break; // loop will retry default: esyslog("ERROR: invalid return value from SetChannel"); } @@ -720,8 +732,17 @@ if (NeedsTransferMode) { if (Device && CanReplay()) { +#ifdef USE_LIVEBUFFER + if(LiveView && !cRecordControls::CanSetLiveChannel(Channel)) + return cRecordControls::IsWritingBuffer() ? srcStillWritingLiveBuffer : scrFailed; +#endif /*USE_LIVEBUFFER*/ 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()! +#ifdef USE_LIVEBUFFER + if(LiveView) + cRecordControls::SetLiveChannel(Device, Channel); + else +#endif /*USE_LIVEBUFFER*/ cControl::Launch(new cTransferControl(Device, Channel)); else Result = scrNoTransfer; diff -Naur vdr-1.7.22/device.h vdr-1.7.22-livebuffer/device.h --- vdr-1.7.22/device.h 2011-12-04 14:38:17.000000000 +0100 +++ vdr-1.7.22-livebuffer/device.h 2011-12-31 11:16:23.000000000 +0100 @@ -32,7 +32,11 @@ #define VOLUMEDELTA 5 // used to increase/decrease the volume #define MAXOCCUPIEDTIMEOUT 99 // max. time (in seconds) a device may be occupied +#ifdef USE_LIVEBUFFER +enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed, srcStillWritingLiveBuffer }; +#else enum eSetChannelResult { scrOk, scrNotAvailable, scrNoTransfer, scrFailed }; +#endif /*USE_LIVEBUFFER*/ enum ePlayMode { pmNone, // audio/video from decoder pmAudioVideo, // audio/video from player diff -Naur vdr-1.7.22/dvbplayer.c vdr-1.7.22-livebuffer/dvbplayer.c --- vdr-1.7.22/dvbplayer.c 2010-03-07 15:24:26.000000000 +0100 +++ vdr-1.7.22-livebuffer/dvbplayer.c 2011-12-31 11:16:23.000000000 +0100 @@ -15,6 +15,9 @@ #include "ringbuffer.h" #include "thread.h" #include "tools.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#endif /*USE_LIVEBUFFER*/ // --- cPtsIndex ------------------------------------------------------------- @@ -35,6 +38,9 @@ void Clear(void); void Put(uint32_t Pts, int Index); int FindIndex(uint32_t Pts); +#ifdef USE_LIVEBUFFER + void SetIndex(int Index) {lastFound = Index;}; +#endif /*USE_LIVEBUFFER*/ }; cPtsIndex::cPtsIndex(void) @@ -205,7 +211,12 @@ cRingBufferFrame *ringBuffer; cPtsIndex ptsIndex; cFileName *fileName; +#ifdef USE_LIVEBUFFER + cIndex *index; + cIndexFile *indexFile; +#else cIndexFile *index; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *replayFile; double framesPerSecond; bool isPesRecording; @@ -270,18 +281,35 @@ dropFrame = NULL; isyslog("replay %s", FileName); fileName = new cFileName(FileName, false, false, isPesRecording); +#ifndef USE_LIVEBUFFER replayFile = fileName->Open(); if (!replayFile) return; +#endif /*USE_LIVEBUFFER*/ ringBuffer = new cRingBufferFrame(PLAYERBUFSIZE); // Create the index file: +#ifdef USE_LIVEBUFFER + indexFile = NULL; + index = cRecordControls::GetLiveIndex(FileName); + if(!index) + index = indexFile = new cIndexFile(FileName, false, isPesRecording); +#else index = new cIndexFile(FileName, false, isPesRecording); +#endif /*USE_LIVEBUFFER*/ if (!index) esyslog("ERROR: can't allocate index"); else if (!index->Ok()) { delete index; index = NULL; } +#ifdef USE_LIVEBUFFER + readIndex = Resume(); + if (readIndex >= 0) { + ptsIndex.SetIndex(readIndex); + isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); + } else + replayFile = fileName->Open(); +#endif /*USE_LIVEBUFFER*/ } cDvbPlayer::~cDvbPlayer() @@ -289,7 +317,11 @@ Save(); Detach(); delete readFrame; // might not have been stored in the buffer in Action() +#ifdef USE_LIVEBUFFER + delete indexFile; +#else delete index; +#endif /*USE_LIVEBUFFER*/ delete fileName; delete ringBuffer; } @@ -387,9 +419,11 @@ uchar *p = NULL; int pc = 0; +#ifndef USE_LIVEBUFFER readIndex = Resume(); if (readIndex >= 0) isyslog("resuming replay at index %d (%s)", readIndex, *IndexToHMSF(readIndex, true, framesPerSecond)); +#endif /*USE_LIVEBUFFER*/ nonBlockingFileReader = new cNonBlockingFileReader; int Length = 0; @@ -436,6 +470,10 @@ if (NewIndex <= 0 && readIndex > 0) NewIndex = 1; // make sure the very first frame is delivered NewIndex = index->GetNextIFrame(NewIndex, playDir == pdForward, &FileNumber, &FileOffset, &Length, TimeShiftMode); +#ifdef USE_LIVEBUFFER + if (NewIndex < 0 && TimeShiftMode) // Why should we wait for a timeout if not pdForward + SwitchToPlayFrame = Index; +#endif if (NewIndex < 0 && TimeShiftMode && playDir == pdForward) SwitchToPlayFrame = Index; Index = NewIndex; @@ -454,6 +492,15 @@ off_t FileOffset; if (index->Get(readIndex + 1, &FileNumber, &FileOffset, &readIndependent, &Length) && NextFile(FileNumber, FileOffset)) readIndex++; +#ifdef USE_LIVEBUFFER + else if(index && index->First() && (readIndex < index->First())) { + int old = readIndex; + readIndex = index->GetNextIFrame(index->First()+1, true, NULL, NULL, NULL, true); + isyslog("Jump before start of livebuffer cortrected %d->%d First %d", old, readIndex, index->First()); + if(readIndex <= index->First()) + eof = true; + } +#endif /*USE_LIVEBUFFER*/ else eof = true; } @@ -587,7 +634,11 @@ else if (Index <= 0 || SwitchToPlayFrame && Index >= SwitchToPlayFrame) SwitchToPlay = true; if (SwitchToPlay) { +#ifdef USE_LIVEBUFFER + if (!SwitchToPlayFrame || (playDir == pdBackward)) +#else if (!SwitchToPlayFrame) +#endif /*USE_LIVEBUFFER*/ Empty(); DevicePlay(); playMode = pmPlay; diff -Naur vdr-1.7.22/livebuffer.c vdr-1.7.22-livebuffer/livebuffer.c --- vdr-1.7.22/livebuffer.c 1970-01-01 01:00:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/livebuffer.c 2011-12-31 11:16:23.000000000 +0100 @@ -0,0 +1,403 @@ +#ifdef USE_LIVEBUFFER +#include "livebuffer.h" +#if VDRVERSNUM >= 10716 + +#include +#include "videodir.h" +#include "recording.h" +#include "skins.h" +#include "player.h" + +#define WAIT_WRITING_COUNT 1000 +#define WAIT_WRITING_SLEEP 10000 + +#define WAIT_TERMINATE_COUNT 300 +#define WAIT_TERMINATE_SLEEP 10000 + +struct tLiveIndex { + int index; + uint64_t offset:40; // up to 1TB per file (not using off_t here - must definitely be exactly 64 bit!) + int reserved:7; // reserved for future use + int independent:1; // marks frames that can be displayed by themselves (for trick modes) + uint16_t number:16; // up to 64K files per recording + tLiveIndex(int Index, bool Independent, uint16_t Number, off_t Offset) + { + index = Index; + offset = Offset; + reserved = 0; + independent = Independent; + number = Number; + } +}; // tLiveIndex + +class cLiveIndex : public cIndex { +public: + cLiveIndex(const char *FileName): bufferFileName(FileName, false), bufferBaseName(FileName) { + resumePos = -1; + lastPos = lastGet = lastBuf = 0; + lastFileNumber=1; + dropFile = false; + maxSize = Setup.LiveBufferSize * 60 * DEFAULTFRAMESPERSECOND; + idx.reserve(maxSize+1); + }; // cLiveIndex + virtual ~cLiveIndex() { + }; // ~cLiveIndex + virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) { + cMutexLock lock(&idx_lock); + idx.push_back(tLiveIndex(++lastPos, Independent, FileNumber, FileOffset)); + while(((idx.size() > maxSize) && (lastGet ? (lastGet > First()) : true) && (lastBuf ? (lastBuf > First()) : true)) || dropFile) { + if(idx.front().number != lastFileNumber) { + isyslog("Deleting old livebuffer file #%d (%d)", lastFileNumber, dropFile); + system(cString::sprintf("ls -l %s/%05d.ts | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", (const char *)bufferBaseName, lastFileNumber)); // for symlink video.xx + unlink(cString::sprintf("%s/%05d.ts", (const char *)bufferBaseName, lastFileNumber)); + lastFileNumber = idx.front().number; + dropFile=false; + } // if + idx.erase(idx.begin()); + } // if + return true; + }; // Write + virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) { + cMutexLock lock(&idx_lock); + std::vector::iterator item = GetIndex(Index); + if(item == idx.end()) return false; + *FileNumber = item->number; + *FileOffset = item->offset; + if (Independent) + *Independent = item->independent; + item++; + if(item == idx.end()) return false; + if (Length) { + uint16_t fn = item->number; + off_t fo = item->offset; + if (fn == *FileNumber) + *Length = int(fo - *FileOffset); + else + *Length = -1; // this means "everything up to EOF" (the buffer's Read function will act accordingly) + } // if + lastGet = Index; + return true; + }; // Get + virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) { + cMutexLock lock(&idx_lock); + std::vector::iterator item = GetIndex(Index); + if(item == idx.end()) { + if(Index < First() && Forward) + item = idx.begin(); + else + return -1; + } + if(Forward) { + do { + item++; + if(item == idx.end()) return -1; + } while(!item->independent); + } else { + do { + if(item == idx.begin()) return -1; + item--; + } while(!item->independent); + } // if + uint16_t fn; + if (!FileNumber) + FileNumber = &fn; + off_t fo; + if (!FileOffset) + FileOffset = &fo; + *FileNumber = item->number; + *FileOffset = item->offset; + item++; + if(item == idx.end()) return -1; + if (Length) { + // all recordings end with a non-independent frame, so the following should be safe: + uint16_t fn = item->number; + off_t fo = item->offset; + if (fn == *FileNumber) { + *Length = int(fo - *FileOffset); + } else { + esyslog("ERROR: 'I' frame at end of file #%d", *FileNumber); + *Length = -1; + } // if + } // if + return Index; + }; // GetNextIFrame + virtual bool SetBufferStart(int Frames) { + cMutexLock lock(&idx_lock); + abortBuf = false; + if(Frames <= 0) { + lastBuf = 0; + return false; + } // if + lastBuf = Last()-Frames; + if(lastBuf < First()) + lastBuf = First(); + lastBuf = GetNextIFrame(lastBuf, true); + return true; + } // SetBufferStart + virtual cUnbufferedFile *GetNextBuffer(int &Length, bool &Independent) { + if(abortBuf || !lastBuf) return NULL; + cMutexLock lock(&idx_lock); + std::vector::iterator buff = GetIndex(lastBuf); + if((buff == idx.end()) || ((buff+1) == idx.end())) return NULL; + off_t offset = buff->offset; + int number = buff->number; + cUnbufferedFile *ret = bufferFileName.SetOffset(number, offset); + Independent = buff->independent; + buff++; + lastBuf = buff->index; + if(number != buff->number) + Length = -1; + else + Length = buff->offset-offset; + return ret; + } // GetNextBuffer + virtual int Get(uint16_t FileNumber, off_t FileOffset) { + for ( std::vector::iterator item = idx.begin(); item != idx.end(); item++) + if (item->number > FileNumber || ((item->number == FileNumber) && off_t(item->offset) >= FileOffset)) + return item->index; + return lastPos; + }; // Get + virtual bool Ok(void) {return true;}; + virtual int First(void) {return idx.size() ? idx.front().index : -1;}; + virtual int Last(void) {return idx.size() ? idx.back().index : -1;}; + virtual void SetResume(int Index) {resumePos = lastGet = Index;}; + virtual int GetResume(void) {return resumePos;}; + virtual bool StoreResume(int Index) {resumePos=Index; lastGet=0; return true;}; + virtual bool IsStillRecording(void) {return true;}; + virtual void Delete(void) {}; + virtual void DropFile(void) {dropFile=true;}; + virtual bool IsWritingBuffer(void) {return lastBuf != 0;}; + virtual void CancelWritingBuffer(void) {abortBuf = true;}; + virtual bool WritingBufferCanceled(void) {return abortBuf;}; +protected: + int firstPos; + int lastPos; + int resumePos; + int lastFileNumber; + int lastGet; + int lastBuf; + bool abortBuf; + bool dropFile; + unsigned int maxSize; + cFileName bufferFileName; + cString bufferBaseName; + cMutex idx_lock; + std::vector idx; + virtual std::vector::iterator GetIndex(int Index) { + if(!idx.size()) return idx.end(); + std::vector::iterator item = idx.begin(); + + unsigned int guess = Index-First(); // Try to guess the position + if(guess > 0) { + if(guess < idx.size()) + item += guess; + else + item = idx.end()-1; + } // if + while(item->index < Index) { + item++; + if(item == idx.end()) + return idx.end(); + } // while + while(item->index > Index) { + if(item == idx.begin()) + return idx.end(); + item--; + } // while + if(item->index != Index) + return idx.end(); + return item; + }; // GetIndex +}; // cLiveIndex + +/*****************************************************************************/ + +cString cLiveRecorder::liveFileName; + +cLiveRecorder::cLiveRecorder(const cChannel *Channel):cRecorder(FileName(), Channel, -1) + ,broken(false) { + handleError = false; + if(index) delete index; + index = new cLiveIndex(FileName()); + Activate(true); +}; // cLiveRecorder::cLiveRecorder + +cLiveRecorder::~cLiveRecorder() { + int maxWait = WAIT_TERMINATE_COUNT; + CancelWritingBuffer(); + while(IsWritingBuffer() && maxWait--) + usleep(WAIT_TERMINATE_SLEEP); + Activate(false); + Cleanup(); +}; // cLiveRecorder::~cLiveRecorder + +bool cLiveRecorder::IsWritingBuffer() { + return index && ((cLiveIndex *)index)->IsWritingBuffer(); +} // cLiveRecorder::IsWritingBuffer + +void cLiveRecorder::CancelWritingBuffer() { + if(index) ((cLiveIndex *)index)->CancelWritingBuffer(); +} // cLiveRecorder::CancelWritingBuffer + +bool cLiveRecorder::NextFile(void) { + if (recordFile && frameDetector->IndependentFrame()) { // every file shall start with an independent frame + if(RunningLowOnDiskSpace() && index) + ((cLiveIndex *)index)->DropFile(); + if (fileSize > MEGABYTE(off_t(Setup.LiveBufferMaxFileSize)) || RunningLowOnDiskSpace()) { + recordFile = fileName->NextFile(); + fileSize = 0; + } // if + } // if + return recordFile != NULL; +} // cLiveRecorder::NextFile + +int cLiveRecorder::LastIFrame() { + if(!index) return 0; + int ret = index->GetNextIFrame(index->Last()-1, false); + return (ret > 0) ? ret : 0; +}; // cLiveRecorder::LastIFrame + +int cLiveRecorder::LastFrame() { + return index ? index->Last() : 0; +}; // cLiveRecorder::LastFrame + +void cLiveRecorder::SetResume(int Index) { + if(index) ((cLiveIndex *)index)->SetResume(Index); +}; // cLiveRecorder::SetResume + +bool cLiveRecorder::SetBufferStart(time_t Start) { + if(!index) return false; + if(time(NULL) <= Start) return false; + int Frames = SecondsToFrames(time(NULL)-Start, frameDetector ? frameDetector->FramesPerSecond() : DEFAULTFRAMESPERSECOND); //test stop livebuffer + return ((cLiveIndex *)index)->SetBufferStart(Frames); +} // cLiveRecorder::SetBufferStart + +cIndex *cLiveRecorder::GetIndex() { + return index; +}; // cLiveRecorder::GetIndex + +bool cLiveRecorder::Cleanup() { + if(FileName()) + if(-1 == system(cString::sprintf("ls -l %s/* | grep -- '->' | sed -e's/.*-> //' | xargs rm -rf", FileName()))) // for symlink video.xx + return false; + else + if(-1 == system(cString::sprintf("rm -rf %s/*", FileName()))) + return false; + return true; +}; // cLiveRecorder::Cleanup + +bool cLiveRecorder::Prepare() { + if (!MakeDirs(FileName(), true)) return false; + return Cleanup(); +}; // cLiveRecorder::Prepare + +const char *cLiveRecorder::FileName() { + if(!(const char *)liveFileName && BufferDirectory) + liveFileName = cString::sprintf("%s/LiveBuffer", BufferDirectory); + return liveFileName; +}; // cLiveRecorder::FileName + +void cLiveRecorder::Activate(bool On) { + cRecorder::Activate(On); + if(!On) broken=true; +} // cLiveRecorder::Activate + +void cLiveRecorder::Receive(uchar *Data, int Length) { + if(broken) { + isyslog("Continue live recorder on broken stream (maybe due to switching to same channel on other device)"); + TsSetTeiOnBrokenPackets(Data, Length); + broken = false; + } // if + cRecorder::Receive(Data, Length); +} // cLiveRecorder::Receive + +/*****************************************************************************/ + +cBufferRecorder::cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex) + :cRecorder(FileName, Channel, Priority) + ,liveBufferIndex(LiveBufferIndex) + ,dropData(false) { + if(liveBufferIndex) dropData=true; // Drop new data till we have written most of the live buffer data +} // cBufferRecorder::cBufferRecorder + +cBufferRecorder::~cBufferRecorder() { + if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); +} // cBufferRecorder::~cBufferRecorder + +void cBufferRecorder::Action(void) { + if(liveBufferIndex) + FillInitialData(NULL, 0); + dropData=false; + cRecorder::Action(); + if(liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; +} // cBufferRecorder::Action + +void cBufferRecorder::Activate(bool On) { + if(!On && liveBufferIndex) ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + cRecorder::Activate(On); +} // cBufferRecorder::Activate + +void cBufferRecorder::Receive(uchar *Data, int Length) { + if(!dropData) cRecorder::Receive(Data, Length); +} // cBufferRecorder::Receive + +void cBufferRecorder::FillInitialData(uchar *Data, int Size) { + if(liveBufferIndex) { + int64_t search_pts = Data ? TsGetPts(Data, Size) : -1; + int maxWait = WAIT_WRITING_COUNT; + uchar buffer[MAXFRAMESIZE]; + int Length; + bool Independent; + bool found = false; + while(!Data || (Size >= TS_SIZE)) { + cUnbufferedFile *file = ((cLiveIndex *)liveBufferIndex)->GetNextBuffer(Length, Independent); + if(!file) { + if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) { + isyslog("Writing buffer canceled by user"); + if(fileSize) TsSetTeiOnBrokenPackets(Data, Size); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + return; + } // if + if(!Data || !Size) return; + if(!maxWait--) + break; + usleep(WAIT_WRITING_SLEEP); + continue; + } // if + if (!NextFile()) + break; + int len = ReadFrame(file, buffer, Length, sizeof(buffer)); + if(len < TS_SIZE) { + isyslog("Failed to read live buffer data"); + break; + } // if + if(Data && Independent && (search_pts == TsGetPts(buffer, len))) { + found = true; + break; + } // if + if (index) + index->Write(Independent, fileName->Number(), fileSize); + if (recordFile->Write(buffer, len) < 0) { + isyslog("Failed to write live buffer data"); + break; + } // if + fileSize += len; + } // while + if(Data) { + isyslog("%lld bytes from live buffer %swritten to recording", fileSize, found ? "seamless ": ""); + if(!found && fileSize) TsSetTeiOnBrokenPackets(Data, Size); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + } else if(((cLiveIndex *)liveBufferIndex)->WritingBufferCanceled()) { + isyslog("%lld bytes from live buffer written to recording (aborted)", fileSize); + ((cLiveIndex *)liveBufferIndex)->SetBufferStart(0); + liveBufferIndex = NULL; + } // if + } else if (Data && fileSize) + TsSetTeiOnBrokenPackets(Data, Size); +} // cBufferRecorder::FillInitialData + +#endif /*VDRVERSNUM*/ +#endif /*USE_LIVEBUFFER*/ diff -Naur vdr-1.7.22/livebuffer.h vdr-1.7.22-livebuffer/livebuffer.h --- vdr-1.7.22/livebuffer.h 1970-01-01 01:00:00.000000000 +0100 +++ vdr-1.7.22-livebuffer/livebuffer.h 2011-12-31 11:16:23.000000000 +0100 @@ -0,0 +1,47 @@ +#ifndef LIVEBUFFER_H +#define LIVEBUFFER_H + +#ifdef USE_LIVEBUFFER +#include "config.h" +#if VDRVERSNUM >= 10716 + +#include "recorder.h" + +class cLiveRecorder : public cRecorder { +public: + cLiveRecorder(const cChannel *Channel); + virtual bool NextFile(void); + virtual ~cLiveRecorder(); + virtual bool IsWritingBuffer(); + virtual void CancelWritingBuffer(); + virtual int LastIFrame(); + virtual int LastFrame(); + virtual void SetResume(int Index); + virtual bool SetBufferStart(time_t Start); + virtual cIndex *GetIndex(); + static bool Cleanup(); + static bool Prepare(); + static const char *FileName(); +protected: + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + bool broken; + static cString liveFileName; +}; // cLiveRecorder + +class cBufferRecorder : public cRecorder { +public: + cBufferRecorder(const char *FileName, const cChannel *Channel, int Priority, cIndex *LiveBufferIndex); + virtual ~cBufferRecorder(); + virtual void FillInitialData(uchar *Data, int Size); +protected: + virtual void Action(void); + virtual void Activate(bool On); + virtual void Receive(uchar *Data, int Length); + cIndex *liveBufferIndex; + bool dropData; +}; // cBufferRecorder + +#endif /*VDRVERSNUM*/ +#endif /*USE_LIVEBUFFER*/ +#endif /*LIVEBUFFER_H*/ diff -Naur vdr-1.7.22/menu.c vdr-1.7.22-livebuffer/menu.c --- vdr-1.7.22/menu.c 2011-12-04 15:52:38.000000000 +0100 +++ vdr-1.7.22-livebuffer/menu.c 2011-12-31 11:16:23.000000000 +0100 @@ -3066,7 +3066,11 @@ class cMenuSetupRecord : public cMenuSetupBase { private: - const char *pauseKeyHandlingTexts[3]; +#ifdef USE_LIVEBUFFER + const char *pauseKeyHandlingTexts[4]; +#else + const char *pauseKeyHandlingTexts[3]; +#endif /*USE_LIVEBUFFER*/ const char *delTimeshiftRecTexts[3]; public: cMenuSetupRecord(void); @@ -3077,6 +3081,9 @@ pauseKeyHandlingTexts[0] = tr("do not pause live video"); pauseKeyHandlingTexts[1] = tr("confirm pause live video"); pauseKeyHandlingTexts[2] = tr("pause live video"); +#ifdef USE_LIVEBUFFER + pauseKeyHandlingTexts[3] = tr("Timeshift"); +#endif /*USE_LIVEBUFFER*/ delTimeshiftRecTexts[0] = tr("no"); delTimeshiftRecTexts[1] = tr("confirm"); delTimeshiftRecTexts[2] = tr("yes"); @@ -3086,7 +3093,12 @@ Add(new cMenuEditIntItem( tr("Setup.Recording$Primary limit"), &data.PrimaryLimit, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default priority"), &data.DefaultPriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Default lifetime (d)"), &data.DefaultLifetime, 0, MAXLIFETIME)); - Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); +#ifdef USE_LIVEBUFFER + Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 4, pauseKeyHandlingTexts)); + Add(new cMenuEditIntItem( tr("Timeshift size (min)"), &data.LiveBufferSize, 1, 300)); // TODO fix name and min/max values +#else + Add(new cMenuEditStraItem(tr("Setup.Recording$Pause key handling"), &data.PauseKeyHandling, 3, pauseKeyHandlingTexts)); +#endif /*USE_LIVEBUFFER*/ Add(new cMenuEditIntItem( tr("Setup.Recording$Pause priority"), &data.PausePriority, 0, MAXPRIORITY)); Add(new cMenuEditIntItem( tr("Setup.Recording$Pause lifetime (d)"), &data.PauseLifetime, 0, MAXLIFETIME)); Add(new cMenuEditBoolItem(tr("Setup.Recording$Use episode name"), &data.UseSubtitle)); @@ -4157,7 +4169,11 @@ isyslog("record %s", fileName); if (MakeDirs(fileName, true)) { const cChannel *ch = timer->Channel(); +#ifdef USE_LIVEBUFFER + recorder = new cBufferRecorder(fileName, ch, timer->Priority(), cRecordControls::GetLiveBuffer(timer)); +#else recorder = new cRecorder(fileName, ch, timer->Priority()); +#endif if (device->AttachReceiver(recorder)) { Recording.WriteInfo(); cStatus::MsgRecording(device, Recording.Name(), Recording.FileName(), true); @@ -4242,6 +4258,10 @@ cRecordControl *cRecordControls::RecordControls[MAXRECORDCONTROLS] = { NULL }; int cRecordControls::state = 0; +#ifdef USE_LIVEBUFFER +cLiveRecorder *cRecordControls::liveRecorder = NULL; +#endif /*USE_LIVEBUFFER*/ + bool cRecordControls::Start(cTimer *Timer, bool Pause) { static time_t LastNoDiskSpaceMessage = 0; @@ -4313,8 +4333,31 @@ } } + +#ifdef USE_LIVEBUFFER +bool cRecordControls::StartLiveBuffer(eKeys Key) { + if(Setup.PauseKeyHandling == 3 && liveRecorder) { + int pos = liveRecorder->LastIFrame(); + isyslog("Enter timeshift at %d / %d", pos, liveRecorder->LastFrame()); + liveRecorder->SetResume(pos?pos:liveRecorder->LastFrame()); + cReplayControl::SetRecording(cLiveRecorder::FileName(), tr("Timeshift mode")); + cReplayControl *rc = new cReplayControl; + cControl::Launch(rc); + cControl::Attach(); + rc->ProcessKey(Key); + rc->Show(); // show progressbar at the start of livebuffer + return true; + } // if + return false; +} // cRecordControls::StartLiveBuffer +#endif /*USE_LIVEBUFFER*/ + bool cRecordControls::PauseLiveVideo(void) { +#ifdef USE_LIVEBUFFER + if(StartLiveBuffer(kPause)) + return true; +#endif /*USE_LIVEBUFFER*/ Skins.Message(mtStatus, tr("Pausing live video...")); cReplayControl::SetRecording(NULL, NULL); // make sure the new cRecordControl will set cReplayControl::LastReplayed() if (Start(NULL, true)) { @@ -4331,6 +4374,54 @@ return false; } +#ifdef USE_LIVEBUFFER +void cRecordControls::SetLiveChannel(cDevice *Device, const cChannel *Channel) { + if(liveRecorder) { + if(Channel && Device && (liveRecorder->ChannelID()==Channel->GetChannelID())) + Device->AttachReceiver(liveRecorder); + else + DELETENULL(liveRecorder); + } // if + if(Device && Channel) cControl::Launch(new cTransferControl(Device, Channel)); + if(Setup.PauseKeyHandling == 3 && Channel && Device && !liveRecorder) { + if (cLiveRecorder::Prepare()) { + liveRecorder = new cLiveRecorder(Channel); + if(!Device->AttachReceiver(liveRecorder)) + DELETENULL(liveRecorder); + } // if + } // if +} // cRecordControls::SetLiveChannel + +bool cRecordControls::CanSetLiveChannel(const cChannel *Channel) { + if(liveRecorder && Channel && (liveRecorder->ChannelID()==Channel->GetChannelID())) return true; + return !IsWritingBuffer(); +} // cRecordControls::CanSetLiveChannel + +bool cRecordControls::IsWritingBuffer() { + return liveRecorder ? liveRecorder->IsWritingBuffer() : false; +} // cRecordControls::IsWritingBuffer + +void cRecordControls::CancelWritingBuffer() { + if(liveRecorder && liveRecorder->IsWritingBuffer()) { + liveRecorder->CancelWritingBuffer(); + sleep(1); // allow recorder to really stop + } // if +} // cRecordControls::CancelWritingBuffer + +cIndex *cRecordControls::GetLiveBuffer(cTimer *Timer) { + if(!liveRecorder || !Timer || !Timer->Channel()) return NULL; + if(!(liveRecorder->ChannelID() == Timer->Channel()->GetChannelID())) return NULL; + if(!liveRecorder->SetBufferStart(Timer->StartTime())) return NULL; + return liveRecorder->GetIndex(); +} // cRecordControls::GetLiveBuffer + +cIndex *cRecordControls::GetLiveIndex(const char *FileName) { + if(!FileName || strcmp(cLiveRecorder::FileName(), FileName)) return NULL; + return liveRecorder ? liveRecorder->GetIndex() : NULL; +} // cRecordControls::GetLiveIndex + +#endif /* USE_LIVEBUFFER */ + const char *cRecordControls::GetInstantId(const char *LastInstantId) { for (int i = 0; i < MAXRECORDCONTROLS; i++) { @@ -4529,21 +4620,30 @@ void cReplayControl::ShowMode(void) { - if (visible || Setup.ShowReplayMode && !cOsd::IsOpen()) { + if (visible || (Setup.ShowReplayMode && !cOsd::IsOpen())) { bool Play, Forward; int Speed; if (GetReplayMode(Play, Forward, Speed) && (!visible || Play != lastPlay || Forward != lastForward || Speed != lastSpeed)) { bool NormalPlay = (Play && Speed == -1); + bool Paused = (!Play && Speed == -1); if (!visible) { if (NormalPlay) return; // no need to do indicate ">" unless there was a different mode displayed before visible = modeOnly = true; + + // if newly paused show full replay osd; ie modeOnly = false + if (Paused) { + modeOnly = (lastPlay == Play); + } + displayReplay = Skins.Current()->DisplayReplay(modeOnly); } - if (modeOnly && !timeoutShow && NormalPlay) + // osd times out when replaying normally OR when paused and full osd is shown + if (!timeoutShow && (NormalPlay|| (!modeOnly && Paused))) timeoutShow = time(NULL) + MODETIMEOUT; + displayReplay->SetMode(Play, Forward, Speed); lastPlay = Play; lastForward = Forward; @@ -4557,6 +4657,45 @@ int Current, Total; if (GetIndex(Current, Total) && Total > 0) { +#ifdef USE_LIVEBUFFER + int first=0; + cIndex *idx = cRecordControls::GetLiveIndex(fileName); + if(idx) first = idx->First(); // Normalize displayed values + Current -= first; + if(Current < 0) Current = 0; + Total -= first; + if(Total < 0) Total = 0; + time_t now = time(NULL); + static time_t last_sched_check = 0; + if(displayReplay && idx && (last_sched_check != now)) { + last_sched_check = now; // Only check every second + cSchedulesLock SchedulesLock; + const cSchedules *Schedules = cSchedules::Schedules(SchedulesLock); + if (Schedules) { + const char *display_title = NULL;// = title; + const cSchedule *Schedule = Schedules->GetSchedule(Channels.GetByNumber(cDevice::CurrentChannel())); + if (Schedule) { + time_t Time = now - round(((double)Total - Current) / FramesPerSecond()); + const cEvent *event = Schedule->GetEventAround(Time); + if (event) display_title = event->Title(); + } // if + + // no event title; show channel name + if (!display_title) { + cChannel *channel = Channels.GetByNumber(cDevice::CurrentChannel()); + display_title = channel->Name(); + } + + // set title as "Timeshift mode: " + // OR "Timeshift mode: " + // if neither is possible leave title as such + if (display_title) + displayReplay->SetTitle(cString::sprintf("%s: %s", + tr("Timeshift mode"), + display_title)); + } // if + } // if +#endif /*USE_LIVEBUFFER*/ if (!visible) { displayReplay = Skins.Current()->DisplayReplay(modeOnly); displayReplay->SetMarks(&marks); @@ -4658,6 +4797,9 @@ void cReplayControl::TimeSearch(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ timeSearchTime = timeSearchPos = 0; timeSearchHide = false; if (modeOnly) @@ -4676,6 +4818,9 @@ void cReplayControl::MarkToggle(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total, true)) { cMark *m = marks.Get(Current); @@ -4696,6 +4841,9 @@ void cReplayControl::MarkJump(bool Forward) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ if (marks.Count()) { int Current, Total; if (GetIndex(Current, Total)) { @@ -4710,6 +4858,9 @@ void cReplayControl::MarkMove(bool Forward) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); @@ -4734,6 +4885,9 @@ void cReplayControl::EditCut(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ if (fileName) { Hide(); if (!cCutter::Active()) { @@ -4752,6 +4906,9 @@ void cReplayControl::EditTest(void) { +#ifdef USE_LIVEBUFFER + if(cRecordControls::GetLiveIndex(fileName)) return; +#endif /*USE_LIVEBUFFER*/ int Current, Total; if (GetIndex(Current, Total)) { cMark *m = marks.Get(Current); @@ -4783,7 +4940,14 @@ if (Key == kNone) marks.Update(); if (visible) { + + if (Key != kNone /*&& !modeOnly*/ && timeoutShow) { + printf("timeout reset +%d\n", MODETIMEOUT); + timeoutShow = time(NULL) + MODETIMEOUT; + } + if (timeoutShow && time(NULL) > timeoutShow) { + printf("timed out \n"); Hide(); ShowMode(); timeoutShow = 0; @@ -4800,12 +4964,34 @@ return osContinue; } bool DoShowMode = true; + +#ifdef USE_LIVEBUFFER + if (cRecordControls::GetLiveIndex(fileName) && (Key >= k0) && (Key <= k9)) + return osSwitchChannel; +#endif /*USE_LIVEBUFFER*/ switch (int(Key)) { // Positioning: +#ifdef USE_LIVEBUFFER + case kUp: if(cRecordControls::GetLiveIndex(fileName)) { + cDevice::SwitchChannel(1); + return osEnd; + } // if + // NO break + case kPlay: + Play(); break; + case kDown: if(cRecordControls::GetLiveIndex(fileName)) { + cDevice::SwitchChannel(-1); + return osEnd; + } // if + // NO break + case kPause: Pause(); + break; +#else case kPlay: case kUp: Play(); break; case kPause: case kDown: Pause(); break; +#endif /*USE_LIVEBUFFER*/ case kFastRew|k_Release: case kLeft|k_Release: if (Setup.MultiSpeedMode) break; @@ -4816,15 +5002,52 @@ if (Setup.MultiSpeedMode) break; case kFastFwd: case kRight: Forward(); break; - case kRed: TimeSearch(); break; + //case kRed: TimeSearch(); break; case kGreen|k_Repeat: case kGreen: SkipSeconds(-60); break; case kYellow|k_Repeat: case kYellow: SkipSeconds( 60); break; +#ifdef USE_LIVEBUFFER + case kRed: if(cRecordControls::GetLiveIndex(fileName)) { + + if (!(visible && !modeOnly)) return osUnknown; + else {} // fall through to case kRecord + // since Timeshift ON and replay OSD is shown + } // if + else { //timeshift off + TimeSearch(); + break; + } // else + // No break + case kRecord: if(cRecordControls::GetLiveIndex(fileName)) { + int frames = 0; + int Current, Total; + if(GetIndex(Current, Total)) + frames = Total-Current; + cTimer *timer = new cTimer(true, false, Channels.GetByNumber(cDevice::CurrentChannel()), frames / FramesPerSecond()); + Timers.Add(timer); + Timers.SetModified(); + if (cRecordControls::Start(timer)) + Skins.Message(mtInfo, tr("Recording started")); + else + Timers.Del(timer); + } // if + break; + case kBlue: if(cRecordControls::GetLiveIndex(fileName)) + if(!(visible && !modeOnly)) + return osUnknown; + //NO break + case kStop: Hide(); + Stop(); + return osEnd; +#else + case kRed: TimeSearch(); break; case kStop: case kBlue: Hide(); Stop(); return osEnd; +#endif /*USE_LIVEBUFFER*/ + default: { DoShowMode = false; switch (int(Key)) { @@ -4855,7 +5078,20 @@ else Show(); break; - case kBack: if (Setup.DelTimeshiftRec) { + case kBack: +#ifdef USE_LIVEBUFFER + if (visible && !modeOnly) { + Hide(); + DoShowMode = true; + break; + } + if(cRecordControls::GetLiveIndex(fileName)) { + Hide(); + Stop(); + return osEnd; + } // if +#endif /*USE_LIVEBUFFER*/ + if (Setup.DelTimeshiftRec) { cRecordControl* rc = cRecordControls::GetRecordControl(fileName); return rc && rc->InstantId() ? osEnd : osRecordings; } diff -Naur vdr-1.7.22/menu.h vdr-1.7.22-livebuffer/menu.h --- vdr-1.7.22/menu.h 2010-03-06 17:15:59.000000000 +0100 +++ vdr-1.7.22-livebuffer/menu.h 2011-12-31 11:16:23.000000000 +0100 @@ -18,6 +18,12 @@ #include "menuitems.h" #include "recorder.h" #include "skins.h" +#ifdef USE_LIVEBUFFER +#include "livebuffer.h" +#endif /*USE_LIVEBUFFER*/ + + + class cMenuText : public cOsdMenu { private: @@ -236,10 +242,18 @@ private: static cRecordControl *RecordControls[]; static int state; +#ifdef USE_LIVEBUFFER +protected: + friend class cRecordControl; + static cLiveRecorder *liveRecorder; +#endif /*USE_LIVEBUFFER*/ public: static bool Start(cTimer *Timer = NULL, bool Pause = false); static void Stop(const char *InstantId); static bool PauseLiveVideo(void); +#ifdef USE_LIVEBUFFER + static bool StartLiveBuffer(eKeys Key); +#endif /*USE_LIVEBUFFER*/ static const char *GetInstantId(const char *LastInstantId); static cRecordControl *GetRecordControl(const char *FileName); static void Process(time_t t); @@ -248,6 +262,14 @@ static void Shutdown(void); static void ChangeState(void) { state++; } static bool StateChanged(int &State); +#ifdef USE_LIVEBUFFER + static void SetLiveChannel(cDevice *Device, const cChannel *Channel); + static bool CanSetLiveChannel(const cChannel *Channel); + static bool IsWritingBuffer(); + static void CancelWritingBuffer(); + static cIndex *GetLiveBuffer(cTimer *Timer); + static cIndex *GetLiveIndex(const char *FileName); +#endif /*USE_LIVEBUFFER*/ }; class cReplayControl : public cDvbPlayerControl { diff -Naur vdr-1.7.22/osdbase.h vdr-1.7.22-livebuffer/osdbase.h --- vdr-1.7.22/osdbase.h 2010-01-16 15:25:31.000000000 +0100 +++ vdr-1.7.22-livebuffer/osdbase.h 2011-12-31 11:16:23.000000000 +0100 @@ -33,6 +33,9 @@ osSwitchDvb, osBack, osEnd, +#ifdef USE_LIVEBUFFER + osSwitchChannel, +#endif /*USE_LIVEBUFFER*/ os_User, // the following values can be used locally osUser1, osUser2, diff -Naur vdr-1.7.22/player.c vdr-1.7.22-livebuffer/player.c --- vdr-1.7.22/player.c 2007-07-20 17:25:24.000000000 +0200 +++ vdr-1.7.22-livebuffer/player.c 2011-12-31 11:16:23.000000000 +0100 @@ -10,6 +10,11 @@ #include "player.h" #include "i18n.h" +#ifdef USE_LIVEBUFFER +#include "menu.h" +#include "transfer.h" +#endif /*USE_LIVEBUFFER*/ + // --- cPlayer --------------------------------------------------------------- cPlayer::cPlayer(ePlayMode PlayMode) @@ -68,6 +73,12 @@ void cControl::Launch(cControl *Control) { +#ifdef USE_LIVEBUFFER + if(!dynamic_cast(Control)) { + if(!dynamic_cast(Control) || strcmp(cLiveRecorder::FileName(), cReplayControl::NowReplaying())) + cRecordControls::SetLiveChannel(NULL, NULL); + } // if +#endif /*USE_LIVEBUFFER*/ cMutexLock MutexLock(&mutex); cControl *c = control; // keeps control from pointing to uninitialized memory control = Control; diff -Naur vdr-1.7.22/po/de_DE.po vdr-1.7.22-livebuffer/po/de_DE.po --- vdr-1.7.22/po/de_DE.po 2011-12-03 16:35:34.000000000 +0100 +++ vdr-1.7.22-livebuffer/po/de_DE.po 2011-12-31 11:16:23.000000000 +0100 @@ -25,6 +25,9 @@ msgid "Can't start Transfer Mode!" msgstr "Transfer-Mode kann nicht gestartet werden!" +msgid "Still writing timeshift data to recording. Abort?" +msgstr "Timeshift-Daten werden noch in Aufnahme kopiert. Abbrechen?" + msgid "off" msgstr "aus" diff -Naur vdr-1.7.22/recorder.c vdr-1.7.22-livebuffer/recorder.c --- vdr-1.7.22/recorder.c 2011-09-04 11:26:44.000000000 +0200 +++ vdr-1.7.22-livebuffer/recorder.c 2011-12-31 11:16:23.000000000 +0100 @@ -24,6 +24,9 @@ cRecorder::cRecorder(const char *FileName, const cChannel *Channel, int Priority) :cReceiver(Channel, Priority) ,cThread("recording") +#ifdef USE_LIVEBUFFER +,handleError(true) +#endif /*USE_LIVEBUFFER*/ { recordingName = strdup(FileName); @@ -140,6 +143,9 @@ InfoWritten = true; } if (FirstIframeSeen || frameDetector->IndependentFrame()) { +#ifdef USE_LIVEBUFFER + if(!FirstIframeSeen) FillInitialData(b, r); +#endif /*USE_LIVEBUFFER*/ FirstIframeSeen = true; // start recording with the first I-frame if (!NextFile()) break; @@ -165,7 +171,11 @@ ringBuffer->Del(Count); } } +#ifdef USE_LIVEBUFFER + if (handleError && (time(NULL) - t > MAXBROKENTIMEOUT)) { +#else if (time(NULL) - t > MAXBROKENTIMEOUT) { +#endif esyslog("ERROR: video data stream broken"); ShutdownHandler.RequestEmergencyExit(); t = time(NULL); diff -Naur vdr-1.7.22/recorder.h vdr-1.7.22-livebuffer/recorder.h --- vdr-1.7.22/recorder.h 2010-12-27 12:17:04.000000000 +0100 +++ vdr-1.7.22-livebuffer/recorder.h 2011-12-31 11:16:23.000000000 +0100 @@ -17,18 +17,33 @@ #include "thread.h" class cRecorder : public cReceiver, cThread { +#ifdef USE_LIVEBUFFER +protected: +#else private: +#endif /*USE_LIVEBUFFER*/ cRingBufferLinear *ringBuffer; cFrameDetector *frameDetector; cPatPmtGenerator patPmtGenerator; cFileName *fileName; +#ifdef USE_LIVEBUFFER + cIndex *index; + bool handleError; +#else cIndexFile *index; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *recordFile; char *recordingName; off_t fileSize; time_t lastDiskSpaceCheck; +#ifdef USE_LIVEBUFFER + virtual bool RunningLowOnDiskSpace(void); + virtual bool NextFile(void); + virtual void FillInitialData(uchar *Data, int Size) {}; +#else bool RunningLowOnDiskSpace(void); bool NextFile(void); +#endif /*USE_LIVEBUFFER*/ protected: virtual void Activate(bool On); virtual void Receive(uchar *Data, int Length); diff -Naur vdr-1.7.22/recording.h vdr-1.7.22-livebuffer/recording.h --- vdr-1.7.22/recording.h 2011-12-04 14:38:17.000000000 +0100 +++ vdr-1.7.22-livebuffer/recording.h 2011-12-31 11:16:23.000000000 +0100 @@ -264,7 +264,26 @@ struct tIndexTs; class cIndexFileGenerator; +#ifdef USE_LIVEBUFFER +class cIndex { +public: + virtual bool Ok(void) =0; + virtual bool Write(bool Independent, uint16_t FileNumber, off_t FileOffset) =0; + virtual bool Get(int Index, uint16_t *FileNumber, off_t *FileOffset, bool *Independent = NULL, int *Length = NULL) =0; + virtual int GetNextIFrame(int Index, bool Forward, uint16_t *FileNumber = NULL, off_t *FileOffset = NULL, int *Length = NULL, bool StayOffEnd = false) =0; + virtual int Get(uint16_t FileNumber, off_t FileOffset) =0; + virtual int First(void) {return 0;}; + virtual int Last(void) =0; + virtual int GetResume(void) =0; + virtual bool StoreResume(int Index) =0; + virtual bool IsStillRecording(void) =0; + virtual void Delete(void) =0; + }; + +class cIndexFile : public cIndex { +#else class cIndexFile { +#endif /*USE_LIVEBUFFER*/ private: int f; cString fileName; diff -Naur vdr-1.7.22/timers.c vdr-1.7.22-livebuffer/timers.c --- vdr-1.7.22/timers.c 2011-08-06 15:13:54.000000000 +0200 +++ vdr-1.7.22-livebuffer/timers.c 2011-12-31 11:16:23.000000000 +0100 @@ -25,7 +25,11 @@ // --- cTimer ---------------------------------------------------------------- +#ifdef USE_LIVEBUFFER +cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel, int Forerun) +#else cTimer::cTimer(bool Instant, bool Pause, cChannel *Channel) +#endif /*USE_LIVEBUFFER*/ { startTime = stopTime = 0; lastSetEvent = 0; @@ -35,7 +39,11 @@ if (Instant) SetFlags(tfActive | tfInstant); channel = Channel ? Channel : Channels.GetByNumber(cDevice::CurrentChannel()); +#ifdef USE_LIVEBUFFER + time_t t = time(NULL) - Forerun; +#else time_t t = time(NULL); +#endif /*USE_LIVEBUFFER*/ struct tm tm_r; struct tm *now = localtime_r(&t, &tm_r); day = SetTime(t, 0); diff -Naur vdr-1.7.22/timers.h vdr-1.7.22-livebuffer/timers.h --- vdr-1.7.22/timers.h 2011-08-06 14:59:32.000000000 +0200 +++ vdr-1.7.22-livebuffer/timers.h 2011-12-31 11:16:23.000000000 +0100 @@ -43,7 +43,11 @@ char *aux; const cEvent *event; public: +#ifdef USE_LIVEBUFFER + cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL, int Forerun = 0); +#else cTimer(bool Instant = false, bool Pause = false, cChannel *Channel = NULL); +#endif /*USE_LIVEBUFFER*/ cTimer(const cEvent *Event); cTimer(const cTimer &Timer); virtual ~cTimer(); diff -Naur vdr-1.7.22/vdr.c vdr-1.7.22-livebuffer/vdr.c --- vdr-1.7.22/vdr.c 2011-12-03 16:35:09.000000000 +0100 +++ vdr-1.7.22-livebuffer/vdr.c 2011-12-31 11:16:23.000000000 +0100 @@ -218,6 +218,9 @@ static struct option long_options[] = { { "audio", required_argument, NULL, 'a' }, +#ifdef USE_LIVEBUFFER + { "buffer", required_argument, NULL, 'b' }, +#endif /* USE_LIVEBUFFER */ { "config", required_argument, NULL, 'c' }, { "daemon", no_argument, NULL, 'd' }, { "device", required_argument, NULL, 'D' }, @@ -251,10 +254,20 @@ }; int c; +#ifdef USE_LIVEBUFFER + while ((c = getopt_long(argc, argv, "a:b:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { +#else while ((c = getopt_long(argc, argv, "a:c:dD:e:E:g:hi:l:L:mp:P:r:s:t:u:v:Vw:", long_options, NULL)) != -1) { +#endif /* USE_LIVEBUFFER */ switch (c) { case 'a': AudioCommand = optarg; break; +#ifdef USE_LIVEBUFFER + case 'b': BufferDirectory = optarg; + if(optarg && *optarg && optarg[strlen(optarg)-1] == '/') + optarg[strlen(optarg)-1] = 0; + break; +#endif /* USE_LIVEBUFFER */ case 'c': ConfigDirectory = optarg; break; case 'd': DaemonMode = true; break; @@ -420,6 +433,9 @@ 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" +#ifdef USE_LIVEBUFFER + " -b DIR, --buffer=DIR use DIR as LiveBuffer directory\n" +#endif /*USE_LIVEBUFFER*/ " -c DIR, --config=DIR read config files from DIR (default: %s)\n" " -d, --daemon run in daemon mode\n" " -D NUM, --device=NUM use only the given DVB device (NUM = 0, 1, 2...)\n" @@ -586,9 +602,12 @@ if (!PluginManager.LoadPlugins(true)) EXIT(2); - // Configuration data: +#ifdef USE_LIVEBUFFER + if (!BufferDirectory) + BufferDirectory = VideoDirectory; +#endif /*USE_LIVEBUFFER*/ if (!ConfigDirectory) ConfigDirectory = DEFAULTCONFDIR; @@ -1091,6 +1110,15 @@ cDisplaySubtitleTracks::Process(key); key = kNone; break; +#ifdef USE_LIVEBUFFER + case kFastRew: + if (!Interact) { + DELETE_MENU; + if(cRecordControls::StartLiveBuffer(key)) + key = kNone; + } // if + break; +#endif /*USE_LIVEBUFFER*/ // Pausing live video: case kPause: if (!cControl::Control()) { @@ -1198,6 +1226,28 @@ else cControl::Shutdown(); break; +#ifdef USE_LIVEBUFFER + case osSwitchChannel: + switch (key) { + // Toggle channels: + case kChanPrev: + case k0: { + if (PreviousChannel[PreviousChannelIndex ^ 1] == LastChannel + || (LastChannel != PreviousChannel[0] && LastChannel != PreviousChannel[1])) + PreviousChannelIndex ^= 1; + Channels.SwitchTo(PreviousChannel[PreviousChannelIndex ^= 1]); + break; + } + case k1 ... k9: + DELETE_MENU; + cControl::Shutdown(); + Menu = new cDisplayChannel(NORMALKEY(key)); + break; + default: + break; + } // switch + break; +#endif /*USE_LIVEBUFFER*/ default: ; } } diff -Naur vdr-1.7.22/videodir.c vdr-1.7.22-livebuffer/videodir.c --- vdr-1.7.22/videodir.c 2008-02-16 14:00:03.000000000 +0100 +++ vdr-1.7.22-livebuffer/videodir.c 2011-12-31 11:16:23.000000000 +0100 @@ -20,6 +20,9 @@ #include "tools.h" const char *VideoDirectory = VIDEODIR; +#ifdef USE_LIVEBUFFER +const char *BufferDirectory = NULL; +#endif /*USE_LIVEBUFFER*/ class cVideoDirectory { private: @@ -106,17 +109,32 @@ cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags) { const char *ActualFileName = FileName; +#ifdef USE_LIVEBUFFER + bool SepBufferDir = false; // Incoming name must be in base video directory: + if (strstr(FileName, VideoDirectory) != FileName) { + if (strstr(FileName, BufferDirectory) == FileName) + SepBufferDir = true; + else { +#else if (strstr(FileName, VideoDirectory) != FileName) { +#endif /*USE_LIVEBUFFER*/ esyslog("ERROR: %s not in %s", FileName, VideoDirectory); errno = ENOENT; // must set 'errno' - any ideas for a better value? return NULL; } +#ifdef USE_LIVEBUFFER + } +#endif /*USE_LIVEBUFFER*/ // Are we going to create a new file? if ((Flags & O_CREAT) != 0) { cVideoDirectory Dir; +#ifdef USE_LIVEBUFFER + if (Dir.IsDistributed() && !SepBufferDir) { +#else if (Dir.IsDistributed()) { +#endif /*USE_LIVEBUFFER*/ // Find the directory with the most free space: int MaxFree = Dir.FreeMB(); while (Dir.Next()) { diff -Naur vdr-1.7.22/videodir.h vdr-1.7.22-livebuffer/videodir.h --- vdr-1.7.22/videodir.h 2008-02-16 13:53:11.000000000 +0100 +++ vdr-1.7.22-livebuffer/videodir.h 2011-12-31 11:16:23.000000000 +0100 @@ -14,6 +14,9 @@ #include "tools.h" extern const char *VideoDirectory; +#ifdef USE_LIVEBUFFER +extern const char *BufferDirectory; +#endif /*USE_LIVEBUFFER*/ cUnbufferedFile *OpenVideoFile(const char *FileName, int Flags); int CloseVideoFile(cUnbufferedFile *File);